Table of contents

  • Introduction
  • Weather Data Visualization
  • Scalar Fields
    • Example: Temperature on a surface
  • Vector Fields
    • Example: Static vector field
    • Examples in html
  • Derivatives and Integrals
    • Derivatives
    • Integrals
    • Html example
  • Differential equations
    • Starting example
    • Second order differential equations
    • Html example
  • Gradient
    • 1D Example
    • 2D Example

Other Links

  • Main page

Language of Physics

  • Show All Code
  • Hide All Code

  • View Source

Introduction

Physics often describes the universe and its phenomena using the language of mathematics, where the Cartesian 3D space serves as the natural stage for physical events. This spatial representation, defined by three orthogonal axes \(x\), \(y\), and \(z\), is the starting point for our exploration. By reducing the dimensionality to 2D, we simplify the analysis, enabling a focused study of structures that arise in such spaces. This reduction is not merely an abstraction but also a practical tool for understanding the behaviors and interactions within physical systems.

Weather Data Visualization

Interactive weather maps serve as an excellent introduction to the fundamental ideas required to study physics. If one can interpret these maps, they will find it easier to grasp the basic concepts of scalar and vector fields, which are central to understanding physical systems.

On such maps, temperature is represented using a color gradient, where each color corresponds to a specific value at a given point in space. This visualization aligns with the definition of a scalar field, which assigns a single numerical value to each point in space.

Similarly, wind patterns are illustrated using moving line segments, where their length and direction represent the speed and direction of the wind at various points. These segments directly depict vector fields, as they assign a vector—defined by both magnitude and direction—to each point in space. Since the magnitude is expressed as a numerical value, the background color can be used to represent the magnitude of the vector field.

www.windy.com

Having this background, we can now delve into the mathematical representations of scalar and vector fields in 2D space. Let us use more formal definitions to understand these concepts better.

Scalar Fields

A scalar field associates a single real value to every point in a given space. In 2D, this can be represented as a function \(T(x, y)\), where \(T\) could represent a physical property like temperature.

Example: Temperature on a surface

Consider a simple model where the temperature of a surface varies with both position and time:

\[ T(x, y) = 5 \sin(x) \cos(y) \]

Below is a Python code of this scalar field:

Show the code
import numpy as np
import matplotlib.pyplot as plt

# Scalar field definition
def scalar_field(x, y):
    return  5 * np.sin(x) * np.cos(y)

# Generate grid points
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
x, y = np.meshgrid(x, y)

# Compute scalar field values
T = scalar_field(x, y)

# Plot the scalar field
plt.figure(figsize=(8, 6))
plt.imshow(T, extent=[-5, 5, -5, 5], origin='lower', cmap="coolwarm")
plt.colorbar(label="Temperature")
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
plt.title("Temperature Distribution on a Surface")
plt.show()

Above example defines a scalar field that do not depend on time. As we can see on weather maps, scalar fields can also be time-dependent, where the value of the field changes with time.

To define a time-dependent scalar field, we can modify the previous example by adding explicit time dependence:

\[ T(x, y, t) = 5 \sin(x) \cos(y) e^{-t} \]

Now the temperature at each point \((x, y)\) decays exponentially with time. To visualize this field, we can use the following Python

Show the code
import numpy as np
import matplotlib.pyplot as plt

# Scalar field definition
def scalar_field(x, y, t):
    return 5 * np.sin(x) * np.cos(y) * np.exp(-t)

# Generate grid points
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
x, y = np.meshgrid(x, y)

# Compute scalar field values for 4 time steps
T = np.zeros((100, 100, 4))
for i in range(4):
    T[:, :, i] = scalar_field(x, y, i)

# Determine common color scale
vmin = np.min(T)
vmax = np.max(T)

# Plot the scalar field for 4 time steps
fig, axs = plt.subplots(2, 2, figsize=(12, 12))

# Store the last image for the colorbar
im = None

for i, ax in enumerate(axs.flat):
    ax.grid(False)  # Explicitly disable gridlines
    im = ax.imshow(T[:, :, i], extent=[-5, 5, -5, 5], origin='lower', cmap="coolwarm", vmin=vmin, vmax=vmax)
    ax.set_title(f"Temperature Distribution at t={i}")
    ax.set_xlabel("X-axis")
    ax.set_ylabel("Y-axis")

# Adjust layout to fit a colorbar outside the plots
fig.subplots_adjust(right=0.85)
cbar_ax = fig.add_axes([0.88, 0.15, 0.02, 0.7])  # Define a new axis for the colorbar
cbar = fig.colorbar(im, cax=cbar_ax)
cbar.set_label("Temperature")

plt.show()

or we can simulate this in an html animation:

html_anim/language_of_physics/scalar_field.html

Vector Fields

A vector field assigns a vector to every point in space. In 2D, such a field can be represented as

\[\vec{F}(x, y) = (F_x(x, y), F_y(x, y))\]

where \(F_x\) and \(F_y\) are the components of the vector field. Both may depend on the position \((x, y)\).

Example: Static vector field

Let us examine a vector field that depends on both space and time:

\[ \vec{F}(x, y) = (\sin(y), \sqrt{\frac{|x|}{5}}) \]

This field might represent the velocity of a fluid at each point \((x, y)\) in a 2D space. Below is a Python code to visualize this vector field:

Show the code
import numpy as np
import matplotlib.pyplot as plt

# Definition of the vector field
def vector_field(x, y):
    Fx = np.sin(y)
    Fy = np.sqrt(np.abs(x)/5)
    return Fx, Fy

# Generating a grid of points
x = np.linspace(-15, 15, 16)
y = np.linspace(-15, 15, 16)
x, y = np.meshgrid(x, y)

# Calculating the vector field
Fx, Fy = vector_field(x, y)

# Calculating the lengths of the vectors
d_lengths = np.sqrt(Fx**2 + Fy**2)

# Initializing the plot
fig, ax = plt.subplots(figsize=(6, 6))
quiver = ax.quiver(
    x, y, Fx, Fy, d_lengths, angles='xy', scale_units='xy', scale=1, cmap='viridis'
)
cb = fig.colorbar(quiver, ax=ax, label="Vector length")
ax.set_xlim(-15, 15)
ax.set_ylim(-15, 15)
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
ax.set_title("2D vector field with color dependent on vector length")

plt.show()

Stream plot

Show the code
import numpy as np
import matplotlib.pyplot as plt

# Definition of the vector field
def vector_field(x, y):
    Fx = np.sin(y)
    Fy = np.sqrt(np.abs(x)/5)
    return Fx, Fy

# Generating a grid of points
x = np.linspace(-15, 15, 100)  # Higher resolution for streamlines
y = np.linspace(-15, 15, 100)
x, y = np.meshgrid(x, y)

# Calculating the vector field
Fx, Fy = vector_field(x, y)
d_lengths = np.sqrt(Fx**2 + Fy**2)

# Streamplot (Streamlines)
fig, ax = plt.subplots(figsize=(6, 6))
stream = ax.streamplot(
    x[0, :], y[:, 0], Fx, Fy, color=d_lengths, cmap='viridis', linewidth=1.5
)
cb = fig.colorbar(stream.lines, ax=ax, label="Vector length")
ax.set_xlim(-15, 15)
ax.set_ylim(-15, 15)
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
ax.set_title("2D Vector Field with Streamlines")
plt.show()

Examples in html

Click and drag to place particles. Pause to arrange them, then Resume.

Derivatives and Integrals

Derivatives

Derivative of functions of one variable

The derivative of a function \(f(x)\) with respect to \(x\) is defined as

\[ \frac{df(x)}{dx} = \lim_{h \to 0} \frac{f(x + h) - f(x)}{h} \]

This limit represents the rate of change of the function \(f(x)\) with respect to \(x\).

Derivative of functions of two variables

The derivative of a function \(f(x, y)\) with respect to \(x\) is defined as

\[ \frac{\partial f(x, y)}{\partial x} = \lim_{h \to 0} \frac{f(x + h, y) - f(x, y)}{h} \]

This limit represents the rate of change of the function \(f(x, y)\) with respect to \(x\). The derivative is a measure of how the function changes as \(x\) changes for a fixed value of \(y\). This describes how the function changes in the \(x\) direction.

Similarly, the derivative of a function \(f(x, y)\) with respect to \(y\) is defined as

\[ \frac{\partial f(x, y)}{\partial y} = \lim_{h \to 0} \frac{f(x, y + h) - f(x, y)}{h} \]

This limit represents the rate of change of the function \(f(x, y)\) with respect to \(y\) and describes how the function changes in the \(y\) direction for a fixed value of \(x\).

Html example

Wizualizacja Pochodnych
Δy / Δx ≈
Exact derivative f'(x):
Function: f(x) = 0.5x³ - x
Move 'h' close to 0 to see the approximation converge to the exact value.
Function: f(x,y) = sin(x) * cos(y)
Red: Slice y=const (∂f/∂x). Blue: Slice x=const (∂f/∂y).

Integrals

Area under a curve

At the simplest level, an integral is the reverse of a derivative. The integral of a function \(f(x)\) with respect to \(x\) is defined as

\[ \int f(x) dx = F(x) + C \]

where \(F(x)\) is the antiderivative of \(f(x)\) and \(C\) is an integration constant. The antiderivative is a function whose derivative is equal to \(f(x)\):

\[ \frac{dF(x)}{dx} = f(x) \]

The integral is a measure of the area under the curve of the function \(f(x)\). The integral is a function that gives the area under the curve of the function \(f(x)\) up to a given point.

Line integrals

The line integral is a generalization of the integral to functions of multiple variables. The line integral of a vector field \(\mathbf{F}(x, y)\) along a curve \(C\) parameterized by \(\mathbf{r}(t) = (x(t), y(t))\) for \(t\) in \([a, b]\) is defined as

\[ \int_C \mathbf{F} \cdot d\mathbf{r} = \int_a^b \mathbf{F}(\mathbf{r}(t)) \cdot \mathbf{r}'(t) dt \]

where \(\mathbf{F} \cdot d\mathbf{r}\) is the dot product of the vector field \(\mathbf{F}\) and the differential element of the curve \(d\mathbf{r}\). The line integral is a measure of the work done by the vector field \(\mathbf{F}\) along the curve \(C\).

Numerically, the line integral can be approximated by dividing the curve into \(N\) small segments and summing the contributions from each segment:

\[ \int_C \mathbf{F} \cdot d\mathbf{r} \approx \sum_{i=1}^{N} \mathbf{F}(\mathbf{r}(t_i)) \cdot \Delta \mathbf{r}_i \]

where \(\Delta \mathbf{r}_i\) is the vector representing the \(i\)-th segment of the curve.

Html example

Area ≈ 0.00 ( f(x) = 1 + 0.1x² + 0.5sin(2x) )
Work ≈ 0.00
Draw with mouse/touch
Red: Force | Green: Displacement

Differential equations

Starting example

Let’s consider the first order differential equation

\[ \frac{dy(x)}{dx} = 2x \]

Let us think how we should read this equation. The left hand side is the derivative of the function \(y(x)\) with respect to \(x\). This derivative is equal to \(2x\). This equation tells us how the function \(y(x)\) changes with \(x\). The function \(y(x)\) changes at a rate of \(2x\) with respect to \(x\). This is a simple differential equation that can be solved by integration. \[ y(x) = x^2 + C \]

where \(C\) is an integration constant. This is the general solution of the differential equation. The solution is not unique because the constant \(C\) can take any value.

Show the code
import numpy as np
import matplotlib.pyplot as plt

def y(x, C):
    return x**2 + C

x = np.linspace(0, 10, 100)

fig, ax = plt.subplots(figsize=(6, 4))

for C in range(-50, 50, 10):
    plt.plot(x, y(x, C), label=f'C={C}')

plt.xlabel('x')
plt.ylabel('y')
plt.show()

Numerical solution

Remember that derivatives can be approximated by finite differences. The derivative of a function \(f(x)\) can be approximated by

\[ \frac{df(x)}{dx} \approx \frac{f(x + h) - f(x)}{h} \]

where \(h\) is a small number. This is a simple way to approximate the derivative of a function. Let’s use this approximation to solve the differential equation

\[ \frac{dy(x)}{dx} = 2x \]

We can approximate the derivative by

\[ \frac{y(x + h) - y(x)}{h} = 2x \]

This equation can be solved for \(y(x + h)\)

\[ y(x + h) = y(x) + 2xh \]

This equation can be used to solve the differential equation numerically. We can start from an initial value of \(y(x)\) and use the equation above to calculate the value of \(y(x + h)\). This value can be used to calculate the next value of \(y(x + 2h)\) and so on:

  • Step 1 - we know value of \(y\) for a given \(x\) which is

\[y(x)\]

  • Step 2 - we can calculate the value of \(y\) for the next point \(x + h\) using

\[y(x + h) = y(x) + 2xh\]

  • Step 3 - we can calculate the value of \(y\) for the next point \(x + 2h\) using

\[y(x + 2h) = y(x + h) + 2(x + h)h\]

  • Step 4 - we can calculate the value of \(y\) for the next point \(x + 3h\) using

\[y(x + 3h) = y(x + 2h) + 2(x + 2h)h\]

  • and so on

Let us compare the numerical solution with the analytical solution.

Show the code
import numpy as np
import matplotlib.pyplot as plt

# Define the derivative dy/dx
def dy_dx(x):
    return 2 * x

# Define the exact analytical solution y(x) for comparison
def y_analytical(x):
    return x**2

# Define a numerical solution for y(x) using the forward Euler method
def y_numerical(x, h):
    y = [0]  # Initialize y with the starting value, assuming y(0) = 0
    for i in range(len(x) - 1):
        y.append(y[-1] + dy_dx(x[i]) * h)  # Update y using dy/dx
    return y

# Set up the x values
plot_x = np.linspace(0, 10, 100)
x = np.linspace(0, 10, 10)
h = x[1] - x[0]  # Step size

# Compute the difference between analytical and numerical solutions
difference = y_analytical(x) - np.array(y_numerical(x, h))

# Set up the figure with two subplots
fig, axs = plt.subplots(2,1, figsize=(10,8))

# Left plot: Analytical and numerical solutions
axs[0].plot(plot_x, y_analytical(plot_x), label='Analytical', linestyle='dashed', linewidth=2)
axs[0].scatter(x, y_numerical(x, h), label='Numerical', linestyle='solid', color='red')
axs[0].set_title('Analytical vs Numerical')
axs[0].set_xlabel('x')
axs[0].set_ylabel('y')
axs[0].legend()

# Right plot: Difference between solutions
axs[1].scatter(x, difference, label='Difference', color='red')
axs[1].set_title('Difference (Analytical - Numerical)')
axs[1].set_xlabel('x')
axs[1].set_ylabel('Difference')
axs[1].legend()

# Adjust layout and show the plots
plt.tight_layout()
plt.show()

Difference between the analytical and numerical solutions depends on the step size \(h\).

Html example

Euler Method Visualization
Step Calculation:
ynew = yold + y' · h
Exact Solution
Numerical (Current)
History
Tangent (y')

Second order differential equations

Let’s consider the second order differential equation

\[ \frac{d^2y(x)}{dx^2} = -y(x) \]

This is a simple differential equation that can be solved by integration. The solution is

\[ y(x) = A \sin(x) + B \cos(x) \]

where \(A\) and \(B\) are integration constants. This is the general solution of the differential equation. The solution is not unique because the constants \(A\) and \(B\) can take any value. The solution is a sinusoidal function. The constants \(A\) and \(B\) determine the amplitude and phase of the sinusoidal function.

Numerical solution

From Taylor’s theorem, we know that the second derivative of a function \(f(x)\) can be approximated by

\[ f(x + h) = f(x) + h \frac{df(x)}{dx} + \frac{h^2}{2} \frac{d^2f(x)}{dx^2} + \ldots \]

also

\[ f(x - h) = f(x) - h \frac{df(x)}{dx} + \frac{h^2}{2} \frac{d^2f(x)}{dx^2} + \ldots \]

adding these two equations we get

\[ f(x + h) + f(x - h) = 2 f(x) + h^2 \frac{d^2f(x)}{dx^2} \]

so we can approximate the second derivative by

\[ \frac{f(x + h) + f(x - h) - 2 f(x)}{h^2} \approx \frac{d^2f(x)}{dx^2} \]

Let’s use this approximation to solve the differential equation

\[ \frac{d^2y(x)}{dx^2} = -y(x) \]

We can approximate the second derivative by

\[ \frac{y(x + h) + y(x - h) - 2 y(x)}{h^2} = -y(x) \]

This equation can be solved for \(y(x + h)\)

\[ y(x + h) = 2 y(x) - y(x - h) - h^2 y(x) \]

This equation can be used to solve the differential equation numerically. We can start from an initial value of \(y(x)\) and \(y(x - h)\) and use the equation above to calculate the value of \(y(x + h)\). This value can be used to calculate the next value of \(y(x + 2h)\) and so on. This is a simple numerical method to solve differential equations.

Let us compare the numerical solution with the analytical solution.

Show the code
import numpy as np
import matplotlib.pyplot as plt

# Define the second derivative d^2y/dx^2
def d2y_dx2(x):
    return -x

# Define the exact analytical solution y(x) for comparison
def y_analytical(x):
    return -np.sin(x)

# Define a numerical solution for y(x) using the forward Euler method
def y_numerical(x, h):
    y = [0, np.sin(-h)]  # Initialize y with the starting values, assuming y(0) = 0 and y(-h) = sin(-h)
    for i in range(1, len(x) - 1):
        y.append(2 * y[-1] - y[-2] - h**2 * y[-1])  # Update y using d^2y/dx^2
    return y

# Set up the x values
plot_x = np.linspace(0, 10, 100)
x = np.linspace(0, 10, 20)
h = x[1] - x[0]  # Step size

# Compute the difference between analytical and numerical solutions
difference = y_analytical(x) - np.array(y_numerical(x, h))

# Set up the figure with two subplots
fig, axs = plt.subplots(2,1, figsize=(10,8))

# Left plot: Analytical and numerical solutions
axs[0].plot(plot_x, y_analytical(plot_x), label='Analytical', linestyle='dashed', linewidth=2)
axs[0].scatter(x, y_numerical(x, h), label='Numerical', color='red')
axs[0].set_title('Analytical vs Numerical')
axs[0].set_xlabel('x')

axs[0].set_ylabel('y')
axs[0].legend()

# Right plot: Difference between solutions
axs[1].scatter(x, difference, label='Difference')
axs[1].set_title('Difference (Analytical - Numerical)')
axs[1].set_xlabel('x')
axs[1].set_ylabel('Difference')
axs[1].legend()

# Adjust layout and show the plots
plt.tight_layout()
plt.show()

Html example

Harmonic Oscillator Visualization
2nd Order Step:
yn+1 = 2yn - yn-1 - h2yn
Exact (Harmonic)
Numerical
Inertia (No force)
Force (-h²y)
History

Gradient

The gradient of a scalar field is itself a vector field that points in the direction of the greatest rate of increase of that scalar field. Its magnitude indicates how rapidly the value of the scalar field changes. Formally, for a function \(f(x, y, z)\), the gradient is:

\[ \nabla f = \left( \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}, \frac{\partial f}{\partial z} \right). \]

If a function \(f\) is defined in 2D space as \(f(x, y)\), then

\[ \nabla f(x, y) = \left( \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y} \right). \]

Any scalar field \(f(x, y)\) has a corresponding gradient field \(\nabla f\). However, not every vector field is necessarily the gradient of some scalar field.


1D Example

For a one-dimensional function \(f(x) = \sin(x)\) on the interval \([0, 10]\), the gradient (or in this 1D case, simply the derivative) is:

\[ f'(x) = \cos(x). \]

Below is a Python example that illustrates how to compute these derivatives at a few sample points. We then represent them as small vectors placed at \(y = 2\) in a plot, pointing upward if \(f'(x)\) is positive and downward if \(f'(x)\) is negative, with a scaled magnitude.

Show the code
import numpy as np
import matplotlib.pyplot as plt

# 1D function
def f(x):
    return np.sin(x)

# Its derivative (gradient in 1D)
def df(x):
    return np.cos(x)

# Sample points on [0, 10]
xs = np.linspace(0, 10, 100)
values_f = f(xs)

# Choose a few points to visualize gradient vectors
sample_points = np.linspace(0, 10, 20)
grad_values = df(sample_points)

fig, ax = plt.subplots(figsize=(8, 4))

# Plot sin(x)
ax.plot(xs, values_f, label='f(x) = sin(x)')

# Plot gradient vectors at y=2 for each sample point
for x_i, grad in zip(sample_points, grad_values):
    # We'll scale the arrow length by 0.5 for visibility
    ax.arrow(
        x_i, f(x_i), grad,0 ,
        head_width=0.1, head_length=0.1, length_includes_head=True,
        fc='green', ec='red'
    )

ax.set_ylim(-1.5, 2)
ax.set_xlabel('x')
ax.set_ylabel('f(x)')
ax.set_title('1D Gradient (Derivative) of sin(x)')
ax.legend()
plt.show()

In this plot: - The blue curve is \(\sin(x)\). - The red arrows at show the derivative \(f'(x) = \cos(x)\) at the chosen sample points. - Arrows pointing up indicate a positive derivative.


2D Example

Now let’s consider a scalar field in two variables. For instance:

\[ f(x, y) = \sin(x)\cos(y). \]

Let us compute partial derivatives of this function. Derivative with respect to \(x\) is:

\[ \frac{\partial f(x,y)}{\partial x} = \frac{\partial}{\partial x}[\sin(x)\cos(y)] = \cos(x)\cos(y). \]

Derivative with respect to \(y\) is:

\[ \frac{\partial f(x,y)}{\partial y} = \frac{\partial}{\partial y}[\sin(x)\cos(y)] = -\sin(x)\sin(y). \]

so the gradient of the function \(f(x, y)\) is:

\[ \nabla f(x, y) = \bigl(\cos(x)\cos(y),\; -\sin(x)\sin(y)\bigr). \]

Show the code
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# Define the 2D scalar field
def f2d(x, y):
    return np.sin(x) * np.cos(y)

# Create a grid for plotting
nx, ny = 60, 60
x_vals = np.linspace(0, 2*np.pi, nx)
y_vals = np.linspace(0, 2*np.pi, ny)
X, Y = np.meshgrid(x_vals, y_vals)
Z = f2d(X, Y)

# --- 3D Surface Plot ---
fig3d = plt.figure(figsize=(8, 6))
ax3d = fig3d.add_subplot(111, projection='3d')

# Create a surface plot
surf = ax3d.plot_surface(
    X, Y, Z, 
    cmap='coolwarm', 
    edgecolor='none',
    alpha=0.9
)
fig3d.colorbar(surf, ax=ax3d, shrink=0.6, label='f(x,y) = sin(x)*cos(y)')
ax3d.set_xlabel('x')
ax3d.set_ylabel('y')
# ax3d.set_zlabel('f(x,y)')
ax3d.set_title('3D Surface of f(x, y) = sin(x)*cos(y)')
plt.show()

Below is a Python example that: 1. Plots a heatmap of \(f(x, y)\). 2. Overlays gradient vectors (arrows) on a grid of points.

Show the code
import numpy as np
import matplotlib.pyplot as plt

# Define the 2D scalar field
def f2d(x, y):
    return np.sin(x) * np.cos(y)

# Define partial derivatives (gradient)
def grad_f2d(x, y):
    # df/dx = cos(x)*cos(y)
    # df/dy = -sin(x)*sin(y)
    return np.cos(x)*np.cos(y), -2*np.sin(x)*np.sin(y)

# Create a grid for plotting
nx, ny = 60, 60
x_vals = np.linspace(0, 2*np.pi, nx)
y_vals = np.linspace(0, 2*np.pi, ny)
X, Y = np.meshgrid(x_vals, y_vals)
Z = f2d(X, Y)

# Compute gradient on a coarser grid for quiver plot
skip = 3
x_quiv = X[::skip, ::skip]
y_quiv = Y[::skip, ::skip]
Fx, Fy = grad_f2d(x_quiv, y_quiv)

fig, ax = plt.subplots(figsize=(8, 6))

# Heatmap of f(x,y)
c = ax.imshow(
    Z, 
    extent=[x_vals.min(), x_vals.max(), y_vals.min(), y_vals.max()],
    origin='lower',
    cmap='coolwarm'
)
fig.colorbar(c, ax=ax, label='f(x,y) = sin(x)*cos(y)')

# Quiver plot of gradient vectors
ax.quiver(
    x_quiv, y_quiv, Fx, Fy, 
    color='black', 
    pivot='mid', 
    alpha=0.8,
    width=0.003,
    scale=50
)

ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('Gradient of f(x, y) = sin(x)*cos(y)')
plt.show()

Interpretation:

  • The heatmap shows the values of \(f(x,y)\). Red/blue corresponds to high/low values of the scalar field.
  • The arrows show the local direction and magnitude of the gradient, i.e., where \(f(x, y)\) increases the most and how quickly.

This demonstrates how any scalar field (like temperature, potential, or pressure) naturally defines a vector field via its gradient. However, not every vector field arises as a gradient of a scalar field—certain mathematical conditions (like having zero curl in a simply connected domain) must be satisfied for a vector field to be the gradient of some scalar field.

Html example

Gradient Descent Explorer 1D

Gradient Descent: Finding the Minimum

Current Calculation
Slope (f'(x)) = 0.00
xnew = 0.00 - (0.1 · Slope)
→ xnew = 0.00
Ball rolls opposite to the slope (Gradient Descent Step).
0.20
Click anywhere on the curve to place the ball, then use controls to descend.
Source Code
---
title: Language of Physics
format:
  html:
    theme: flatly
    grid:
      body-width: 1000px      # Domyślnie jest to ok. 800-900px
      margin-width: 250px  
    toc: true
    toc-depth: 3
    highlight-style: tango
    code-line-numbers: true
    code-fold: true
    code-summary: "Show the code"
    code-tools: true
    code-block-bg: "rgba(42, 174, 42, 0.02)"
    code-block-border-left: "#2aae2a"
    code-language-label: true
    css: styles.css
    math: mathjax
    self-contained: true
    other-links:
      - text: Main page
        href: https://dchorazkiewicz.github.io/Mathematics_Physics_Lectures
---

## Introduction

Physics often describes the universe and its phenomena using the language of mathematics, where the Cartesian 3D space serves as the natural stage for physical events. This spatial representation, defined by three orthogonal axes $x$, $y$, and $z$, is the starting point for our exploration. By reducing the dimensionality to 2D, we simplify the analysis, enabling a focused study of structures that arise in such spaces. This reduction is not merely an abstraction but also a practical tool for understanding the behaviors and interactions within physical systems.


## Weather Data Visualization

Interactive weather maps serve as an excellent introduction to the fundamental ideas required to study physics. If one can interpret these maps, they will find it easier to grasp the basic concepts of scalar and vector fields, which are central to understanding physical systems.

On such maps, temperature is represented using a color gradient, where each color corresponds to a specific value at a given point in space. This visualization aligns with the definition of a **scalar field**, which assigns a single numerical value to each point in space. 

Similarly, wind patterns are illustrated using moving line segments, where their length and direction represent the speed and direction of the wind at various points. These segments directly depict **vector fields**, as they assign a vector—defined by both magnitude and direction—to each point in space. Since the magnitude is expressed as a numerical value, the background color can be used to represent the magnitude of the vector field.


```{=html}
<iframe width="650" height="450" src="https://embed.windy.com/embed2.html" frameborder="0" data-external="1"></iframe>

```

[www.windy.com](https://www.windy.com/)


Having this background, we can now delve into the mathematical representations of scalar and vector fields in 2D space. Let us use more formal definitions to understand these concepts better.

## Scalar Fields

A scalar field associates a single real value to every point in a given space. In 2D, this can be represented as a function $T(x, y)$, where $T$ could represent a physical property like temperature.

### Example: Temperature on a surface

Consider a simple model where the temperature of a surface varies with both position and time:

$$
T(x, y) = 5 \sin(x) \cos(y)
$$

Below is a Python code of this scalar field:

```{python}
import numpy as np
import matplotlib.pyplot as plt

# Scalar field definition
def scalar_field(x, y):
    return  5 * np.sin(x) * np.cos(y)

# Generate grid points
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
x, y = np.meshgrid(x, y)

# Compute scalar field values
T = scalar_field(x, y)

# Plot the scalar field
plt.figure(figsize=(8, 6))
plt.imshow(T, extent=[-5, 5, -5, 5], origin='lower', cmap="coolwarm")
plt.colorbar(label="Temperature")
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
plt.title("Temperature Distribution on a Surface")
plt.show()

```

Above example defines a scalar field that do not depend on time. As we can see on weather maps, scalar fields can also be time-dependent, where the value of the field changes with time.

To define a time-dependent scalar field, we can modify the previous example by adding explicit time dependence:

$$
T(x, y, t) = 5 \sin(x) \cos(y) e^{-t}
$$

Now the temperature at each point $(x, y)$ decays exponentially with time. To visualize this field, we can use the following Python

```{python}
import numpy as np
import matplotlib.pyplot as plt

# Scalar field definition
def scalar_field(x, y, t):
    return 5 * np.sin(x) * np.cos(y) * np.exp(-t)

# Generate grid points
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
x, y = np.meshgrid(x, y)

# Compute scalar field values for 4 time steps
T = np.zeros((100, 100, 4))
for i in range(4):
    T[:, :, i] = scalar_field(x, y, i)

# Determine common color scale
vmin = np.min(T)
vmax = np.max(T)

# Plot the scalar field for 4 time steps
fig, axs = plt.subplots(2, 2, figsize=(12, 12))

# Store the last image for the colorbar
im = None

for i, ax in enumerate(axs.flat):
    ax.grid(False)  # Explicitly disable gridlines
    im = ax.imshow(T[:, :, i], extent=[-5, 5, -5, 5], origin='lower', cmap="coolwarm", vmin=vmin, vmax=vmax)
    ax.set_title(f"Temperature Distribution at t={i}")
    ax.set_xlabel("X-axis")
    ax.set_ylabel("Y-axis")

# Adjust layout to fit a colorbar outside the plots
fig.subplots_adjust(right=0.85)
cbar_ax = fig.add_axes([0.88, 0.15, 0.02, 0.7])  # Define a new axis for the colorbar
cbar = fig.colorbar(im, cax=cbar_ax)
cbar.set_label("Temperature")

plt.show()

```

or we can simulate this in an html animation:

<iframe src="html_anim/language_of_physics/scalar_field.html" width="100%" height="500px"></iframe>
[html_anim/language_of_physics/scalar_field.html](html_anim/language_of_physics/scalar_field.html)

## Vector Fields

A vector field assigns a vector to every point in space. In 2D, such a field can be represented as 

$$\vec{F}(x, y) = (F_x(x, y), F_y(x, y))$$

where $F_x$ and $F_y$ are the components of the vector field. Both may depend on the position $(x, y)$.

### Example: Static vector field

Let us examine a vector field that depends on both space and time:

$$
\vec{F}(x, y) = (\sin(y), \sqrt{\frac{|x|}{5}})
$$

This field might represent the velocity of a fluid at each point $(x, y)$ in a 2D space. Below is a Python code to visualize this vector field:

```{python}
import numpy as np
import matplotlib.pyplot as plt

# Definition of the vector field
def vector_field(x, y):
    Fx = np.sin(y)
    Fy = np.sqrt(np.abs(x)/5)
    return Fx, Fy

# Generating a grid of points
x = np.linspace(-15, 15, 16)
y = np.linspace(-15, 15, 16)
x, y = np.meshgrid(x, y)

# Calculating the vector field
Fx, Fy = vector_field(x, y)

# Calculating the lengths of the vectors
d_lengths = np.sqrt(Fx**2 + Fy**2)

# Initializing the plot
fig, ax = plt.subplots(figsize=(6, 6))
quiver = ax.quiver(
    x, y, Fx, Fy, d_lengths, angles='xy', scale_units='xy', scale=1, cmap='viridis'
)
cb = fig.colorbar(quiver, ax=ax, label="Vector length")
ax.set_xlim(-15, 15)
ax.set_ylim(-15, 15)
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
ax.set_title("2D vector field with color dependent on vector length")

plt.show()
```


Stream plot

```{python}
import numpy as np
import matplotlib.pyplot as plt

# Definition of the vector field
def vector_field(x, y):
    Fx = np.sin(y)
    Fy = np.sqrt(np.abs(x)/5)
    return Fx, Fy

# Generating a grid of points
x = np.linspace(-15, 15, 100)  # Higher resolution for streamlines
y = np.linspace(-15, 15, 100)
x, y = np.meshgrid(x, y)

# Calculating the vector field
Fx, Fy = vector_field(x, y)
d_lengths = np.sqrt(Fx**2 + Fy**2)

# Streamplot (Streamlines)
fig, ax = plt.subplots(figsize=(6, 6))
stream = ax.streamplot(
    x[0, :], y[:, 0], Fx, Fy, color=d_lengths, cmap='viridis', linewidth=1.5
)
cb = fig.colorbar(stream.lines, ax=ax, label="Vector length")
ax.set_xlim(-15, 15)
ax.set_ylim(-15, 15)
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
ax.set_title("2D Vector Field with Streamlines")
plt.show()
```

### Examples in html

```{=html}
<div class="river-simulation">
    <style>
        /* Zmieniono selektor z 'body' na klasę kontenera, aby nie psuć reszty strony */
        .river-simulation {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0;
            padding: 10px;
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: #f4f4f4;
            color: #333;
            user-select: none; /* To teraz działa tylko wewnątrz symulacji */
        }

        .river-simulation .container {
            background-color: white;
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            max-width: 800px;
            width: 100%;
            text-align: center;
        }

        .river-simulation canvas {
            border-left: 0;
            border-right: 0;
            background-color: #e3f2fd;
            cursor: crosshair;
            margin: 0 auto;
            width: 100%;
            max-width: 600px;
            height: 300px;
            display: block;
            touch-action: none; /* Important for touch dragging */
        }

        .river-simulation .river-bank {
            height: 20px;
            width: 100%;
            max-width: 600px;
            background-color: #5d4037;
            margin: 0 auto;
            position: relative;
            z-index: 1;
        }
        .river-simulation .bank-top { border-radius: 4px 4px 0 0; border-bottom: 2px solid #3e2723; }
        .river-simulation .bank-bottom { border-radius: 0 0 4px 4px; border-top: 2px solid #3e2723; }

        .river-simulation .controls {
            margin: 15px 0;
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 15px;
            flex-wrap: wrap;
        }

        .river-simulation button {
            padding: 8px 16px;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 600;
            transition: opacity 0.2s;
            min-width: 100px;
        }
        
        .river-simulation button:hover { opacity: 0.9; }

        .river-simulation .btn-pause { background-color: #f57c00; }
        .river-simulation .btn-resume { background-color: #2e7d32; }
        .river-simulation .btn-clear { background-color: #d32f2f; }

        .river-simulation .slider-group {
            display: flex;
            align-items: center;
            gap: 10px;
            background: #f5f5f5;
            padding: 5px 10px;
            border-radius: 4px;
        }

        .river-simulation label {
            font-weight: 600;
            font-size: 0.9em;
        }

        .river-simulation input[type=range] {
            cursor: pointer;
        }

        .river-simulation .status {
            font-size: 0.85em;
            color: #666;
            margin-top: 5px;
            font-style: italic;
        }
    </style>

    <div class="container">
        <div class="controls">
            <button id="pauseBtn" class="btn-pause">Pause</button>
            <button id="clearBtn" class="btn-clear">Clear Particles</button>
            
            <div class="slider-group">
                <label for="speedSlider">Flow Speed (V<sub>max</sub>): <span id="speedVal">5.0</span></label>
                <input type="range" id="speedSlider" min="0" max="15" step="0.5" value="5">
            </div>
        </div>

        <div class="river-bank bank-top"></div>
        <canvas id="simulationCanvas" width="600" height="300"></canvas>
        <div class="river-bank bank-bottom"></div>

        <p class="status">Click and drag to place particles. Pause to arrange them, then Resume.</p>
    </div>

    <script>
        (function() { // Funkcja IIFE, żeby zmienne nie wyciekały globalnie
            const canvas = document.getElementById('simulationCanvas');
            const ctx = canvas.getContext('2d');
            const speedSlider = document.getElementById('speedSlider');
            const speedVal = document.getElementById('speedVal');
            const clearBtn = document.getElementById('clearBtn');
            const pauseBtn = document.getElementById('pauseBtn');

            // Simulation State
            let particles = [];
            let maxSpeed = parseFloat(speedSlider.value);
            let isPaused = false;
            let isDragging = false;
            
            // Constants
            const GRID_SPACING = 40;
            const PARTICLE_COLOR = '#d32f2f';
            const VECTOR_COLOR = 'rgba(0, 60, 160, 0.4)';

            // --- Physics & Math ---

            // Velocity profile: V(y) = Vmax * sin(y_normalized * PI)
            function getVelocity(y) {
                const H = canvas.height;
                // Map y to 0..PI range
                const arg = (y / H) * Math.PI;
                // Prevent negative speeds (just in case)
                if (arg < 0 || arg > Math.PI) return 0;
                return maxSpeed * Math.sin(arg);
            }

            // --- Drawing ---

            function drawArrow(x, y, vel) {
                const visualScale = 6.0;
                const length = vel * visualScale;
                
                // Do not draw tiny arrows
                if (length < 2) return;

                ctx.strokeStyle = VECTOR_COLOR;
                ctx.fillStyle = VECTOR_COLOR;
                ctx.lineWidth = 2;

                ctx.beginPath();
                ctx.moveTo(x, y);
                ctx.lineTo(x + length, y);
                ctx.stroke();

                // Arrowhead
                const headSize = 5;
                ctx.beginPath();
                ctx.moveTo(x + length, y);
                ctx.lineTo(x + length - headSize, y - headSize * 0.6);
                ctx.lineTo(x + length - headSize, y + headSize * 0.6);
                ctx.fill();
            }

            function drawVectorField() {
                // Draw a grid of vectors
                for (let y = GRID_SPACING / 2; y < canvas.height; y += GRID_SPACING) {
                    for (let x = GRID_SPACING / 2; x < canvas.width; x += GRID_SPACING) {
                        const v = getVelocity(y);
                        drawArrow(x, y, v);
                    }
                }
            }

            function drawParticles() {
                ctx.fillStyle = PARTICLE_COLOR;
                for (let p of particles) {
                    ctx.beginPath();
                    ctx.arc(p.x, p.y, 5, 0, Math.PI * 2);
                    ctx.fill();
                }
            }

            // --- Main Loop ---

            function update() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                // 1. Draw Background (Vector Field)
                drawVectorField();

                // 2. Physics Update (only if running)
                if (!isPaused) {
                    for (let p of particles) {
                        const v = getVelocity(p.y);
                        p.x += v;
                    }
                    // Remove particles that went off-screen
                    particles = particles.filter(p => p.x < canvas.width + 10);
                }

                // 3. Draw Particles
                drawParticles();
                requestAnimationFrame(update);
            }

            // --- Interaction ---

            function addParticle(clientX, clientY) {
                const rect = canvas.getBoundingClientRect();
                const scaleX = canvas.width / rect.width;
                const scaleY = canvas.height / rect.height;

                const x = (clientX - rect.left) * scaleX;
                const y = (clientY - rect.top) * scaleY;

                // Simple collision check to prevent stacking too many particles in one spot
                const tooClose = particles.some(p => Math.abs(p.x - x) < 5 && Math.abs(p.y - y) < 5);
                if (!tooClose) {
                    particles.push({ x, y });
                }
            }

            // Mouse Events
            canvas.addEventListener('mousedown', (e) => {
                isDragging = true;
                addParticle(e.clientX, e.clientY);
            });
            window.addEventListener('mousemove', (e) => {
                if (isDragging) {
                    addParticle(e.clientX, e.clientY);
                }
            });
            window.addEventListener('mouseup', () => {
                isDragging = false;
            });

            // Touch Events (for mobile/tablet)
            canvas.addEventListener('touchstart', (e) => {
                e.preventDefault(); // Prevent scrolling
                isDragging = true;
                addParticle(e.touches[0].clientX, e.touches[0].clientY);
            }, { passive: false });
            
            canvas.addEventListener('touchmove', (e) => {
                e.preventDefault(); 
                if (isDragging) {
                    addParticle(e.touches[0].clientX, e.touches[0].clientY);
                }
            }, { passive: false });
            
            window.addEventListener('touchend', () => {
                isDragging = false;
            });

            // UI Controls
            pauseBtn.addEventListener('click', () => {
                isPaused = !isPaused;
                if (isPaused) {
                    pauseBtn.innerText = "Resume";
                    pauseBtn.className = "btn-resume";
                } else {
                    pauseBtn.innerText = "Pause";
                    pauseBtn.className = "btn-pause";
                }
            });

            clearBtn.addEventListener('click', () => {
                particles = [];
            });

            speedSlider.addEventListener('input', (e) => {
                maxSpeed = parseFloat(e.target.value);
                speedVal.innerText = maxSpeed.toFixed(1);
            });

            // Start
            update();
        })();
    </script>
</div>
```

## Derivatives and Integrals

### Derivatives

#### Derivative of functions of one variable

The derivative of a function $f(x)$ with respect to $x$ is defined as

$$
\frac{df(x)}{dx} = \lim_{h \to 0} \frac{f(x + h) - f(x)}{h}
$$

This limit represents the rate of change of the function $f(x)$ with respect to $x$.

#### Derivative of functions of two variables

The derivative of a function $f(x, y)$ with respect to $x$ is defined as

$$
\frac{\partial f(x, y)}{\partial x} = \lim_{h \to 0} \frac{f(x + h, y) - f(x, y)}{h}
$$

This limit represents the rate of change of the function $f(x, y)$ with respect to $x$. The derivative is a measure of how the function changes as $x$ changes for a fixed value of $y$. This describes how the function changes in the $x$ direction.

Similarly, the derivative of a function $f(x, y)$ with respect to $y$ is defined as

$$
\frac{\partial f(x, y)}{\partial y} = \lim_{h \to 0} \frac{f(x, y + h) - f(x, y)}{h}
$$

This limit represents the rate of change of the function $f(x, y)$ with respect to $y$ and describes how the function changes in the $y$ direction for a fixed value of $x$.

#### Html example

```{=html}
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Wizualizacja Pochodnych</title>
    <!-- Import Plotly -->
    <script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
    <style>
        body {
            margin: 0;
            padding: 0;
            background-color: #f4f4f4;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            display: flex;
            justify-content: center;
            align-items: flex-start;
            min-height: 100vh;
        }

        .derivative-widget {
            width: 100%;
            max-width: 1000px;
            margin: 20px;
            padding: 10px;
            background-color: #f4f4f4;
            color: #333;
            display: flex;
            flex-direction: column;
            align-items: center;
            border-radius: 8px;
        }

        .derivative-widget .vis-container {
            width: 100%;
            background-color: white;
            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
            border-radius: 8px;
            display: flex;
            flex-direction: column;
            height: 650px;
        }

        /* Tabs styling */
        .derivative-widget .tabs {
            display: flex;
            background-color: #e0e0e0;
            border-radius: 8px 8px 0 0;
        }

        .derivative-widget .tab-btn {
            flex: 1;
            padding: 15px;
            border: none;
            background-color: transparent;
            cursor: pointer;
            font-size: 16px;
            font-weight: 600;
            transition: background-color 0.3s;
            border-bottom: 3px solid transparent;
        }

        .derivative-widget .tab-btn:hover {
            background-color: #d0d0d0;
        }

        .derivative-widget .tab-btn.active {
            background-color: white;
            border-bottom: 3px solid #1976d2;
            color: #1976d2;
        }

        /* Content styling */
        .derivative-widget .tab-content {
            display: none;
            flex: 1;
            padding: 20px;
            flex-direction: column;
            overflow: hidden;
        }

        .derivative-widget .tab-content.active {
            display: flex;
        }

        .derivative-widget .controls {
            display: flex;
            gap: 20px;
            margin-bottom: 15px;
            align-items: center;
            flex-wrap: wrap;
            padding: 15px;
            background-color: #f9f9f9;
            border-radius: 8px;
            border: 1px solid #eee;
            justify-content: space-between; /* Rozsunięcie elementów */
        }

        .derivative-widget .control-group {
            display: flex;
            flex-direction: column;
            min-width: 150px;
        }

        /* Styl dla boksu z wynikami */
        .derivative-widget .result-box {
            background-color: #fff;
            padding: 10px 15px;
            border-radius: 6px;
            border-left: 4px solid #1976d2;
            box-shadow: 0 2px 5px rgba(0,0,0,0.05);
            min-width: 220px;
        }

        .derivative-widget label {
            font-size: 0.9em;
            font-weight: 600;
            margin-bottom: 5px;
            color: #555;
        }

        .derivative-widget input[type=range] {
            cursor: pointer;
        }

        .derivative-widget .plot-area {
            flex: 1;
            width: 100%;
            min-height: 400px;
            position: relative;
        }

        .derivative-widget .math-val {
            font-family: monospace;
            color: #d32f2f;
            font-weight: bold;
        }
        
        .derivative-widget .math-result {
            font-family: monospace;
            color: #1976d2;
            font-weight: bold;
            font-size: 1.2em;
        }
    </style>
</head>
<body>

<div class="derivative-widget">
    <div class="vis-container">
        <div class="tabs">
            <button class="tab-btn active" data-tab="ordinary">Ordinary Derivative (1D)</button>
            <button class="tab-btn" data-tab="partial">Partial Derivatives (2D/3D)</button>
        </div>

        <div id="tab-ordinary" class="tab-content active">
            <div class="controls">
                <div style="display:flex; gap: 20px; flex-wrap: wrap;">
                    <div class="control-group">
                        <label>Point position (x): <span id="ord-x-val" class="math-val">1.00</span></label>
                        <input type="range" id="ord-x" min="-2.5" max="2.5" step="0.05" value="1.0">
                    </div>
                    <div class="control-group">
                        <label>Step size (h): <span id="ord-h-val" class="math-val">1.00</span></label>
                        <input type="range" id="ord-h" min="0.01" max="2.0" step="0.01" value="1.0">
                    </div>
                </div>
                
                <!-- Boks z obliczeniami -->
                <div class="control-group result-box">
                    <div title="Iloraz różnicowy (nachylenie siecznej)">
                        &Delta;y / &Delta;x &approx; <span id="calc-slope" class="math-result"></span>
                    </div>
                    <div style="font-size: 0.85em; color: #666; margin-top: 4px;">
                        Exact derivative f'(x): <span id="calc-exact" style="font-weight:600"></span>
                    </div>
                </div>

                <div style="width: 100%; font-size: 0.85em; color: #666; margin-top: 10px;">
                    Function: <span style="font-family: monospace">f(x) = 0.5x³ - x</span><br>
                    Move 'h' close to 0 to see the approximation converge to the exact value.
                </div>
            </div>
            <div id="plot-ordinary" class="plot-area"></div>
        </div>

        <div id="tab-partial" class="tab-content">
            <div class="controls">
                <div class="control-group">
                    <label>X position: <span id="part-x-val" class="math-val">1.00</span></label>
                    <input type="range" id="part-x" min="-2.5" max="2.5" step="0.1" value="1.0">
                </div>
                <div class="control-group">
                    <label>Y position: <span id="part-y-val" class="math-val">1.00</span></label>
                    <input type="range" id="part-y" min="-2.5" max="2.5" step="0.1" value="1.0">
                </div>
                <div style="font-size: 0.85em; color: #666;">
                    Function: <span style="font-family: monospace">f(x,y) = sin(x) * cos(y)</span><br>
                    <span style="color:red">Red:</span> Slice y=const (∂f/∂x).
                    <span style="color:blue">Blue:</span> Slice x=const (∂f/∂y).
                </div>
            </div>
            <div id="plot-partial" class="plot-area"></div>
        </div>
    </div>
</div>

<script>
    (function() {
        const widget = document.querySelector('.derivative-widget');
        
        function switchTab(tabName) {
            widget.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
            widget.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active'));
            
            widget.querySelector('#tab-' + tabName).classList.add('active');
            widget.querySelector(`.tab-btn[data-tab="${tabName}"]`).classList.add('active');
            
            window.dispatchEvent(new Event('resize'));
        }

        widget.querySelectorAll('.tab-btn').forEach(btn => {
            btn.addEventListener('click', function() {
                switchTab(this.getAttribute('data-tab'));
            });
        });

        // --- TAB 1 LOGIC: ORDINARY DERIVATIVE ---
        const ordXSlider = document.getElementById('ord-x');
        const ordHSlider = document.getElementById('ord-h');
        const ordXVal = document.getElementById('ord-x-val');
        const ordHVal = document.getElementById('ord-h-val');
        
        // Elementy do wyświetlania obliczeń
        const calcSlope = document.getElementById('calc-slope');
        const calcExact = document.getElementById('calc-exact');

        function f1(x) { return 0.5 * Math.pow(x, 3) - x; }
        function df1(x) { return 1.5 * Math.pow(x, 2) - 1; }

        function drawOrdinary() {
            const x0 = parseFloat(ordXSlider.value);
            const h = parseFloat(ordHSlider.value);
            const x1 = x0 + h;

            ordXVal.innerText = x0.toFixed(2);
            ordHVal.innerText = h.toFixed(2);

            const xRange = [], yRange = [];
            for (let i = -3.5; i <= 3.5; i += 0.1) {
                xRange.push(i);
                yRange.push(f1(i));
            }
            const y0 = f1(x0);
            const y1 = f1(x1);
            
            // Secant calculation (Difference Quotient)
            const m_secant = (y1 - y0) / (x1 - x0);
            
            // Tangent calculation (Exact Derivative)
            const m_tangent = df1(x0);

            // Aktualizacja wyświetlacza obliczeń
            calcSlope.innerText = m_secant.toFixed(4);
            calcExact.innerText = m_tangent.toFixed(4);

            const secantX = [x0 - 2, x1 + 2];
            const secantY = [m_secant * (secantX[0] - x0) + y0, m_secant * (secantX[1] - x0) + y0];
            
            const tangentX = [x0 - 1.5, x0 + 1.5];
            const tangentY = [m_tangent * (tangentX[0] - x0) + y0, m_tangent * (tangentX[1] - x0) + y0];
            
            const triX = [x0, x1, x1, x0];
            const triY = [y0, y0, y1, y0];

            const data = [
                { x: xRange, y: yRange, mode: 'lines', line: {color: '#444', width: 2}, name: 'f(x)' },
                { x: triX, y: triY, mode: 'lines', line: {color: 'rgba(0,0,0,0.3)', width: 1, dash: 'dot'}, fill: 'toself', fillColor: 'rgba(0,255,0,0.1)', name: 'Δx, Δy', showlegend: false },
                { x: secantX, y: secantY, mode: 'lines', line: {color: '#2e7d32', width: 3}, name: 'Secant (Approx)' },
                { x: tangentX, y: tangentY, mode: 'lines', line: {color: '#d32f2f', width: 2, dash: 'dash'}, name: 'Tangent (Exact)' },
                { x: [x0], y: [y0], mode: 'markers', marker: {color: '#d32f2f', size: 10}, name: 'x' },
                { x: [x1], y: [y1], mode: 'markers', marker: {color: '#2e7d32', size: 10}, name: 'x + h' }
            ];
            
            const layout = {
                title: 'Difference Quotient vs Derivative',
                xaxis: {title: 'x', range: [-3, 3]},
                yaxis: {title: 'f(x)', range: [-3, 3]},
                margin: {l: 50, r: 20, t: 40, b: 40},
                showlegend: true,
                legend: {x: 0, y: 1},
                autosize: true
            };

            // Używamy react dla płynności
            Plotly.react('plot-ordinary', data, layout, {responsive: true, displayModeBar: false});
        }

        ordXSlider.addEventListener('input', drawOrdinary);
        ordHSlider.addEventListener('input', drawOrdinary);


        // --- TAB 2 LOGIC: PARTIAL DERIVATIVES ---
        const partXSlider = document.getElementById('part-x');
        const partYSlider = document.getElementById('part-y');
        const partXVal = document.getElementById('part-x-val');
        const partYVal = document.getElementById('part-y-val');

        function f2(x, y) { return Math.sin(x) * Math.cos(y); }
        function df2_dx(x, y) { return Math.cos(x) * Math.cos(y); }
        function df2_dy(x, y) { return -Math.sin(x) * Math.sin(y); }

        function drawPartial() {
            const x0 = parseFloat(partXSlider.value);
            const y0 = parseFloat(partYSlider.value);
            const z0 = f2(x0, y0);

            partXVal.innerText = x0.toFixed(2);
            partYVal.innerText = y0.toFixed(2);

            const range = 3.5;
            const step = 0.2;
            const z_data = [], x_axis = [], y_axis = [];
            for (let y = -range; y <= range; y += step) {
                const row = [];
                y_axis.push(y);
                for (let x = -range; x <= range; x += step) {
                    if(y === -range) x_axis.push(x);
                    row.push(f2(x, y));
                }
                z_data.push(row);
            }

            const sliceX_x=[], sliceX_y=[], sliceX_z=[];
            for(let i=-range; i<=range; i+=0.1) { sliceX_x.push(i); sliceX_y.push(y0); sliceX_z.push(f2(i, y0)); }
            
            const sliceY_x=[], sliceY_y=[], sliceY_z=[];
            for(let i=-range; i<=range; i+=0.1) { sliceY_x.push(x0); sliceY_y.push(i); sliceY_z.push(f2(x0, i)); }

            const slopeX = df2_dx(x0, y0);
            const tanX_x = [x0 - 1, x0 + 1], tanX_y = [y0, y0], tanX_z = [z0 - slopeX, z0 + slopeX];
            
            const slopeY = df2_dy(x0, y0);
            const tanY_x = [x0, x0], tanY_y = [y0 - 1, y0 + 1], tanY_z = [z0 - slopeY, z0 + slopeY];

            const data = [
                { z: z_data, x: x_axis, y: y_axis, type: 'surface', colorscale: 'Viridis', opacity: 0.6, showscale: false, name: 'Surface' },
                { type: 'scatter3d', mode: 'lines', x: sliceX_x, y: sliceX_y, z: sliceX_z, line: {width: 5, color: 'red'}, name: 'Cut y=const' },
                { type: 'scatter3d', mode: 'lines', x: tanX_x, y: tanX_y, z: tanX_z, line: {width: 8, color: '#ff8a80'}, name: 'Tangent X' },
                { type: 'scatter3d', mode: 'lines', x: sliceY_x, y: sliceY_y, z: sliceY_z, line: {width: 5, color: 'blue'}, name: 'Cut x=const' },
                { type: 'scatter3d', mode: 'lines', x: tanY_x, y: tanY_y, z: tanY_z, line: {width: 8, color: '#82b1ff'}, name: 'Tangent Y' },
                { type: 'scatter3d', mode: 'markers', x: [x0], y: [y0], z: [z0], marker: {size: 6, color: 'black'}, name: 'Point P' }
            ];

            const layout = {
                title: 'Partial Derivatives (Slices)',
                margin: {l: 0, r: 0, b: 0, t: 30},
                scene: { aspectmode: 'cube', xaxis: {title: 'X'}, yaxis: {title: 'Y'}, zaxis: {title: 'Z'} },
                showlegend: true,
                legend: {x: 0, y: 1},
                autosize: true
            };
            Plotly.react('plot-partial', data, layout, {responsive: true, displayModeBar: false});
        }

        partXSlider.addEventListener('input', drawPartial);
        partYSlider.addEventListener('input', drawPartial);

        drawOrdinary();
        setTimeout(drawPartial, 100);
        
        window.addEventListener('resize', () => {
            Plotly.Plots.resize('plot-ordinary');
            Plotly.Plots.resize('plot-partial');
        });
    })();
</script>

</body>
</html>
```

### Integrals

#### Area under a curve

At the simplest level, an integral is the reverse of a derivative. The integral of a function $f(x)$ with respect to $x$ is defined as

$$
\int f(x) dx = F(x) + C
$$

where $F(x)$ is the antiderivative of $f(x)$ and $C$ is an integration constant. The antiderivative is a function whose derivative is equal to $f(x)$:

$$
\frac{dF(x)}{dx} = f(x)
$$

The integral is a measure of the area under the curve of the function $f(x)$. The integral is a function that gives the area under the curve of the function $f(x)$ up to a given point.
 

#### Line integrals

The line integral is a generalization of the integral to functions of multiple variables. The line integral of a vector field $\mathbf{F}(x, y)$ along a curve $C$ parameterized by $\mathbf{r}(t) = (x(t), y(t))$ for $t$ in $[a, b]$ is defined as

$$
\int_C \mathbf{F} \cdot d\mathbf{r} = \int_a^b \mathbf{F}(\mathbf{r}(t)) \cdot \mathbf{r}'(t) dt
$$

where $\mathbf{F} \cdot d\mathbf{r}$ is the dot product of the vector field $\mathbf{F}$ and the differential element of the curve $d\mathbf{r}$. The line integral is a measure of the work done by the vector field $\mathbf{F}$ along the curve $C$.

Numerically, the line integral can be approximated by dividing the curve into $N$ small segments and summing the contributions from each segment:

$$  
\int_C \mathbf{F} \cdot d\mathbf{r} \approx \sum_{i=1}^{N} \mathbf{F}(\mathbf{r}(t_i)) \cdot \Delta \mathbf{r}_i
$$

where $\Delta \mathbf{r}_i$ is the vector representing the $i$-th segment of the curve.


### Html example

```{=html}
<!-- Load Plotly once per page. If your Quarto doc already loads it, you can remove this script tag. -->
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>

<div class="integration-vis-container" style="font-family: sans-serif; border: 1px solid #ddd; border-radius: 8px; background: #fff; margin: 20px 0; overflow: hidden; max-width: 100%;">
    
    <!-- TABS HEADER -->
    <div style="display: flex; background: #f5f5f5; border-bottom: 1px solid #ddd;">
        <button id="btn-tab-riemann" style="flex: 1; padding: 15px; border: none; background: transparent; cursor: pointer; font-weight: bold; color: #555; border-bottom: 3px solid transparent;">
            1. Definite Integral (Area)
        </button>
        <button id="btn-tab-line" style="flex: 1; padding: 15px; border: none; background: transparent; cursor: pointer; font-weight: bold; color: #555; border-bottom: 3px solid transparent;">
            2. Line Integral (Work)
        </button>
    </div>

    <!-- TAB 1: RIEMANN SUMS -->
    <div id="content-riemann" style="display: block; padding: 15px;">
        <div style="background: #fafafa; padding: 10px; border: 1px solid #eee; margin-bottom: 10px; border-radius: 4px;">
            <label style="font-weight: bold;">Rectangles (N): <span id="disp-rie-n">5</span></label>
            <input type="range" id="input-rie-n" min="2" max="50" step="1" value="5" style="vertical-align: middle; margin-left: 10px;">
            <div style="margin-top: 5px; font-family: monospace; color: #333;">
                Area ≈ <span id="disp-rie-sum" style="color: #d32f2f; font-weight: bold;">0.00</span>
                <span style="color: #777; font-size: 0.9em; margin-left: 10px;">( f(x) = 1 + 0.1x² + 0.5sin(2x) )</span>
            </div>
        </div>
        <!-- IMPORTANT: Explicit height prevents collapse -->
        <div id="plot-riemann-div" style="width: 100%; height: 400px;"></div>
    </div>

    <!-- TAB 2: LINE INTEGRAL -->
    <div id="content-line" style="display: none; padding: 15px;">
        <div style="background: #fafafa; padding: 10px; border: 1px solid #eee; margin-bottom: 10px; border-radius: 4px; display: flex; align-items: center; flex-wrap: wrap; gap: 15px;">
            <div>
                <label style="font-weight: bold;">Segments (N): <span id="disp-line-n">10</span></label>
                <input type="range" id="input-line-n" min="2" max="40" step="1" value="10" style="vertical-align: middle;">
            </div>
            <button id="btn-clear-path" style="background: #d32f2f; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; font-weight: bold;">Clear Path</button>
            <div style="font-family: monospace; color: #333;">
                Work ≈ <span id="disp-line-work" style="color: #d32f2f; font-weight: bold;">0.00</span>
            </div>
        </div>
        
        <!-- Canvas Container with explicit relative positioning -->
        <div id="canvas-wrapper" style="width: 100%; height: 400px; position: relative; border: 1px solid #eee; background: #fff;">
            <canvas id="canvas-line" style="display: block; width: 100%; height: 100%; cursor: crosshair; touch-action: none;"></canvas>
            <div style="position: absolute; top: 10px; left: 10px; background: rgba(255,255,255,0.9); padding: 5px 8px; pointer-events: none; border: 1px solid #ccc; font-size: 0.85em; border-radius: 4px;">
                Draw with mouse/touch<br>
                <span style="color: #d32f2f; font-weight: bold;">Red: Force</span> | <span style="color: #2e7d32; font-weight: bold;">Green: Displacement</span>
            </div>
        </div>
    </div>

</div>

<script>
// Use IIFE to isolate variables from the rest of the page
(function() {
    // --- ELEMENTS ---
    const btnTabRie = document.getElementById('btn-tab-riemann');
    const btnTabLine = document.getElementById('btn-tab-line');
    const contentRie = document.getElementById('content-riemann');
    const contentLine = document.getElementById('content-line');
    
    const inputRieN = document.getElementById('input-rie-n');
    const dispRieN = document.getElementById('disp-rie-n');
    const dispRieSum = document.getElementById('disp-rie-sum');
    const plotRieDiv = document.getElementById('plot-riemann-div');

    const inputLineN = document.getElementById('input-line-n');
    const dispLineN = document.getElementById('disp-line-n');
    const dispLineWork = document.getElementById('disp-line-work');
    const btnClearPath = document.getElementById('btn-clear-path');
    const canvas = document.getElementById('canvas-line');
    const canvasWrapper = document.getElementById('canvas-wrapper');
    const ctx = canvas.getContext('2d');

    // --- TAB LOGIC ---
    function setActiveTab(tab) {
        if (tab === 'riemann') {
            contentRie.style.display = 'block';
            contentLine.style.display = 'none';
            btnTabRie.style.borderBottomColor = '#1976d2';
            btnTabRie.style.color = '#1976d2';
            btnTabLine.style.borderBottomColor = 'transparent';
            btnTabLine.style.color = '#555';
            
            // Trigger Plotly resize after tab switch
            if (window.Plotly) Plotly.Plots.resize(plotRieDiv);
        } else {
            contentRie.style.display = 'none';
            contentLine.style.display = 'block';
            btnTabRie.style.borderBottomColor = 'transparent';
            btnTabRie.style.color = '#555';
            btnTabLine.style.borderBottomColor = '#1976d2';
            btnTabLine.style.color = '#1976d2';
            
            resizeCanvas();
        }
    }

    btnTabRie.onclick = () => setActiveTab('riemann');
    btnTabLine.onclick = () => setActiveTab('line');

    // ==========================================
    // 1. RIEMANN SUMS
    // ==========================================
    function f(x) {
        return 1 + 0.1 * x * x + 0.5 * Math.sin(2 * x);
    }

    function updateRiemann() {
        const N = parseInt(inputRieN.value);
        dispRieN.innerText = N;
        
        const a = 0, b = 6;
        const dx = (b - a) / N;
        
        // Curve
        const xSmooth = [], ySmooth = [];
        for(let x=a; x<=b; x+=0.05) {
            xSmooth.push(x);
            ySmooth.push(f(x));
        }

        // Rectangles
        const xRect = [], yRect = [];
        let area = 0;
        for(let i=0; i<N; i++) {
            let xi = a + i*dx;
            let val = f(xi);
            xRect.push(xi + dx/2);
            yRect.push(val);
            area += val * dx;
        }
        
        dispRieSum.innerText = area.toFixed(4);

        const data = [
            {
                x: xRect, y: yRect, width: dx*0.9, type: 'bar',
                marker: { color: 'rgba(50, 171, 96, 0.6)', line: {color: 'rgba(50, 171, 96, 1)', width: 1} },
                name: 'Riemann Sum'
            },
            {
                x: xSmooth, y: ySmooth, mode: 'lines',
                line: { color: '#1976d2', width: 3 },
                name: 'f(x)'
            }
        ];

        const layout = {
            margin: { t: 10, l: 40, r: 10, b: 30 },
            xaxis: { title: 'x' },
            yaxis: { title: 'f(x)', range: [0, 5] },
            showlegend: false,
            autosize: true
        };

        Plotly.newPlot(plotRieDiv, data, layout, {responsive: true, displayModeBar: false});
    }

    inputRieN.addEventListener('input', updateRiemann);

    // ==========================================
    // 2. LINE INTEGRAL (CANVAS)
    // ==========================================
    let pathPoints = [];
    let isDrawing = false;

    function resizeCanvas() {
        const rect = canvasWrapper.getBoundingClientRect();
        if(rect.width > 0 && rect.height > 0) {
            canvas.width = rect.width;
            canvas.height = rect.height;
            drawScene();
        }
    }

    function getField(cx, cy, w, h) {
        // Simple rotation field relative to center
        const mx = (cx - w/2) / (w/4);
        const my = -(cy - h/2) / (h/4); // Y inverted for math
        return { fx: -my, fy: mx * 0.5 };
    }

    function drawArrow(x, y, dx, dy, color) {
        const head = 6;
        const angle = Math.atan2(dy, dx);
        ctx.beginPath();
        ctx.strokeStyle = color;
        ctx.lineWidth = 2;
        ctx.moveTo(x, y);
        ctx.lineTo(x + dx, y + dy);
        ctx.stroke();
        
        ctx.beginPath();
        ctx.fillStyle = color;
        ctx.moveTo(x + dx, y + dy);
        ctx.lineTo(x + dx - head*Math.cos(angle-Math.PI/6), y + dy - head*Math.sin(angle-Math.PI/6));
        ctx.lineTo(x + dx - head*Math.cos(angle+Math.PI/6), y + dy - head*Math.sin(angle+Math.PI/6));
        ctx.fill();
    }

    function drawScene() {
        const w = canvas.width;
        const h = canvas.height;
        ctx.clearRect(0,0,w,h);

        // Grid (Field)
        const step = 40;
        for(let y=20; y<h; y+=step) {
            for(let x=20; x<w; x+=step) {
                const f = getField(x,y,w,h);
                drawArrow(x, y, f.fx*15, -f.fy*15, '#e0e0e0');
            }
        }

        // Path
        if(pathPoints.length > 1) {
            ctx.beginPath();
            ctx.strokeStyle = '#1976d2';
            ctx.lineWidth = 2;
            ctx.moveTo(pathPoints[0].x, pathPoints[0].y);
            for(let i=1; i<pathPoints.length; i++) ctx.lineTo(pathPoints[i].x, pathPoints[i].y);
            ctx.stroke();
        }

        // Vectors & Work
        if(pathPoints.length > 1) {
            const N = parseInt(inputLineN.value);
            dispLineN.innerText = N;

            // Calc Lengths
            let dists = [0], totalLen = 0;
            for(let i=1; i<pathPoints.length; i++) {
                let dx = pathPoints[i].x - pathPoints[i-1].x;
                let dy = pathPoints[i].y - pathPoints[i-1].y;
                totalLen += Math.sqrt(dx*dx + dy*dy);
                dists.push(totalLen);
            }

            // Resample
            let work = 0;
            let samplePts = [];
            for(let k=0; k<=N; k++) {
                let target = (k/N) * totalLen;
                // Find index
                let idx = 0;
                while(idx < dists.length-1 && dists[idx+1] < target) idx++;
                let t = (target - dists[idx]) / (dists[idx+1] - dists[idx] || 1);
                let p1 = pathPoints[idx];
                let p2 = pathPoints[idx+1] || p1;
                samplePts.push({
                    x: p1.x + (p2.x - p1.x)*t,
                    y: p1.y + (p2.y - p1.y)*t
                });
            }

            // Draw
            for(let i=0; i<N; i++) {
                let p1 = samplePts[i];
                let p2 = samplePts[i+1];
                let dx = p2.x - p1.x;
                let dy = p2.y - p1.y;
                
                let mx = (p1.x+p2.x)/2;
                let my = (p1.y+p2.y)/2;
                let f = getField(mx, my, w, h);
                
                // Work calc (normalized)
                work += (f.fx * (dx/100) + f.fy * (-dy/100));

                drawArrow(p1.x, p1.y, dx, dy, '#2e7d32'); // Disp
                drawArrow(p1.x, p1.y, f.fx*25, -f.fy*25, '#d32f2f'); // Force
                
                ctx.fillStyle='black'; ctx.beginPath(); ctx.arc(p1.x, p1.y, 3, 0, 6.28); ctx.fill();
            }
            dispLineWork.innerText = (work*5).toFixed(2);
        }
    }

    // Interaction
    function getXY(e) {
        const r = canvas.getBoundingClientRect();
        const clientX = e.touches ? e.touches[0].clientX : e.clientX;
        const clientY = e.touches ? e.touches[0].clientY : e.clientY;
        return { x: clientX - r.left, y: clientY - r.top };
    }

    canvas.addEventListener('mousedown', e => { isDrawing=true; pathPoints=[getXY(e)]; drawScene(); });
    window.addEventListener('mousemove', e => { 
        if(!isDrawing) return; 
        pathPoints.push(getXY(e)); 
        drawScene(); 
    });
    window.addEventListener('mouseup', () => { isDrawing=false; drawScene(); });

    // Touch
    canvas.addEventListener('touchstart', e => { 
        e.preventDefault(); // Stop scroll inside canvas ONLY
        isDrawing=true; 
        pathPoints=[getXY(e)]; 
        drawScene(); 
    }, {passive:false});
    
    window.addEventListener('touchmove', e => { 
        if(isDrawing) {
             e.preventDefault(); 
             pathPoints.push(getXY(e)); 
             drawScene(); 
        }
    }, {passive:false});
    
    window.addEventListener('touchend', () => isDrawing=false);

    btnClearPath.onclick = () => { pathPoints=[]; dispLineWork.innerText="0.00"; drawScene(); };
    inputLineN.oninput = drawScene;

    // INIT
    setActiveTab('riemann');
    updateRiemann();
    // Safety timeout for Plotly to render after DOM placement
    setTimeout(() => {
        updateRiemann();
        resizeCanvas();
    }, 500);

})();
</script>
```


## Differential equations

### Starting example

Let's consider the first order differential equation

$$
\frac{dy(x)}{dx} = 2x
$$

Let us think how we should read this equation. The left hand side is the derivative of the function $y(x)$ with respect to $x$. This derivative is equal to $2x$. This equation tells us how the function $y(x)$ changes with $x$. The function $y(x)$ changes at a rate of $2x$ with respect to $x$. This is a simple differential equation that can be solved by integration.
$$
y(x) = x^2 + C
$$

where $C$ is an integration constant. This is the general solution of the differential equation. The solution is not unique because the constant $C$ can take any value. 

```{python}
import numpy as np
import matplotlib.pyplot as plt

def y(x, C):
    return x**2 + C

x = np.linspace(0, 10, 100)

fig, ax = plt.subplots(figsize=(6, 4))

for C in range(-50, 50, 10):
    plt.plot(x, y(x, C), label=f'C={C}')

plt.xlabel('x')
plt.ylabel('y')
plt.show()
```


##### Numerical solution

Remember that derivatives can be approximated by finite differences. The derivative of a function $f(x)$ can be approximated by

$$
\frac{df(x)}{dx} \approx \frac{f(x + h) - f(x)}{h}
$$

where $h$ is a small number. This is a simple way to approximate the derivative of a function. Let's use this approximation to solve the differential equation

$$
\frac{dy(x)}{dx} = 2x
$$

We can approximate the derivative by

$$
\frac{y(x + h) - y(x)}{h} = 2x
$$

This equation can be solved for $y(x + h)$

$$
y(x + h) = y(x) + 2xh
$$

This equation can be used to solve the differential equation numerically. We can start from an initial value of $y(x)$ and use the equation above to calculate the value of $y(x + h)$. This value can be used to calculate the next value of $y(x + 2h)$ and so on:

* Step 1 -  we know value of $y$ for a given $x$ which is 

$$y(x)$$

* Step 2 - we can calculate the value of $y$ for the next point $x + h$ using 

$$y(x + h) = y(x) + 2xh$$

* Step 3 - we can calculate the value of $y$ for the next point $x + 2h$ using 

$$y(x + 2h) = y(x + h) + 2(x + h)h$$

* Step 4 - we can calculate the value of $y$ for the next point $x + 3h$ using 

$$y(x + 3h) = y(x + 2h) + 2(x + 2h)h$$

* and so on

Let us compare the numerical solution with the analytical solution.

```{python}
import numpy as np
import matplotlib.pyplot as plt

# Define the derivative dy/dx
def dy_dx(x):
    return 2 * x

# Define the exact analytical solution y(x) for comparison
def y_analytical(x):
    return x**2

# Define a numerical solution for y(x) using the forward Euler method
def y_numerical(x, h):
    y = [0]  # Initialize y with the starting value, assuming y(0) = 0
    for i in range(len(x) - 1):
        y.append(y[-1] + dy_dx(x[i]) * h)  # Update y using dy/dx
    return y

# Set up the x values
plot_x = np.linspace(0, 10, 100)
x = np.linspace(0, 10, 10)
h = x[1] - x[0]  # Step size

# Compute the difference between analytical and numerical solutions
difference = y_analytical(x) - np.array(y_numerical(x, h))

# Set up the figure with two subplots
fig, axs = plt.subplots(2,1, figsize=(10,8))

# Left plot: Analytical and numerical solutions
axs[0].plot(plot_x, y_analytical(plot_x), label='Analytical', linestyle='dashed', linewidth=2)
axs[0].scatter(x, y_numerical(x, h), label='Numerical', linestyle='solid', color='red')
axs[0].set_title('Analytical vs Numerical')
axs[0].set_xlabel('x')
axs[0].set_ylabel('y')
axs[0].legend()

# Right plot: Difference between solutions
axs[1].scatter(x, difference, label='Difference', color='red')
axs[1].set_title('Difference (Analytical - Numerical)')
axs[1].set_xlabel('x')
axs[1].set_ylabel('Difference')
axs[1].legend()

# Adjust layout and show the plots
plt.tight_layout()
plt.show()

```

Difference between the analytical and numerical solutions depends on the step size $h$. 

#### Html example

```{=html}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Euler Method Visualization</title>
    <style>
        body {
            margin: 0;
            padding: 20px;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #fff;
            display: flex;
            justify-content: center;
        }

        /* Namespace .euler-method-sim to isolate styles */
        .euler-method-sim {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0 auto;
            padding: 10px;
            background-color: #f9f9f9;
            border: 1px solid #ddd;
            border-radius: 8px;
            color: #333;
            width: 100%;
            max-width: 800px;
            box-sizing: border-box;
        }

        .euler-method-sim .sim-container {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 15px;
        }

        /* Wrapper must be relative for absolute positioning of the math panel */
        .euler-method-sim .canvas-wrapper {
            position: relative; 
            width: 100%;
            max-width: 650px;
            background: white;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.05);
        }

        .euler-method-sim canvas {
            display: block;
            width: 100%;
            height: auto;
            aspect-ratio: 16 / 10;
            cursor: default;
        }

        /* --- Math Panel Styles --- */
        .euler-method-sim .math-panel {
            position: absolute;
            top: 10px;
            left: 10px;
            background-color: rgba(255, 255, 255, 0.95);
            border: 1px solid #1976d2;
            border-left: 5px solid #1976d2;
            padding: 10px 15px;
            border-radius: 4px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            pointer-events: none;
            z-index: 10;
            display: none;
            line-height: 1.6;
        }

        .euler-method-sim .math-label {
            color: #666;
            font-weight: bold;
            font-size: 11px;
            text-transform: uppercase;
            margin-bottom: 2px;
            display: block;
            border-bottom: 1px solid #eee;
        }

        .euler-method-sim .math-row {
            white-space: nowrap;
        }

        .euler-method-sim .val-old { color: #d32f2f; font-weight: bold; }
        .euler-method-sim .val-slope { color: #f57f17; font-weight: bold; }
        .euler-method-sim .val-h { color: #333; font-weight: bold; }
        .euler-method-sim .val-new { color: #1976d2; font-weight: bold; }

        /* Controls */
        .euler-method-sim .controls {
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
            justify-content: center;
            width: 100%;
            padding: 15px;
            background: #eee;
            border-radius: 6px;
        }

        .euler-method-sim .control-group {
            display: flex;
            flex-direction: column;
            align-items: center;
            min-width: 120px;
        }

        .euler-method-sim label {
            font-size: 0.9em;
            font-weight: 600;
            margin-bottom: 5px;
        }

        .euler-method-sim .val-display {
            font-family: monospace;
            color: #d32f2f;
            font-weight: bold;
        }

        .euler-method-sim .btn-group {
            display: flex;
            gap: 10px;
            align-items: center;
        }

        .euler-method-sim button {
            padding: 8px 16px;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            font-size: 14px;
            transition: background 0.2s;
            height: 40px;
        }

        .euler-method-sim #btn-restart { background-color: #1976d2; }
        .euler-method-sim #btn-restart:hover { background-color: #1565c0; }
        .euler-method-sim #btn-restart:disabled { background-color: #90caf9; cursor: not-allowed; }

        .euler-method-sim #btn-clear { background-color: #757575; }
        .euler-method-sim #btn-clear:hover { background-color: #616161; }
        
        .euler-method-sim input[type=range] {
            cursor: pointer;
        }

        .euler-method-sim .legend {
            font-size: 0.85em;
            display: flex;
            gap: 15px;
            margin-top: 5px;
            flex-wrap: wrap;
            justify-content: center;
        }
        
        .euler-method-sim .legend-item {
            display: flex;
            align-items: center;
            gap: 5px;
        }
        
        .euler-method-sim .dot {
            width: 10px; height: 10px; border-radius: 50%;
        }
        .euler-method-sim .line-sample {
            width: 20px; height: 3px; border-radius: 2px;
        }
    </style>
</head>
<body>

<div class="euler-method-sim">
    <div class="sim-container">
        <div class="controls">
            <div class="control-group">
                <label>Initial y(0): <span id="disp-y0" class="val-display">0.0</span></label>
                <input type="range" id="input-y0" min="-5" max="5" step="0.5" value="0">
            </div>
            <div class="control-group">
                <label>Step size (h): <span id="disp-h" class="val-display">1.0</span></label>
                <input type="range" id="input-h" min="0.1" max="2.0" step="0.1" value="1.0">
            </div>
            <div class="control-group">
                <label>Animation Speed</label>
                <input type="range" id="input-speed" min="1" max="10" step="1" value="5">
            </div>
            <div class="btn-group">
                <button id="btn-restart">Start</button>
                <button id="btn-clear">Clear History</button>
            </div>
        </div>

        <div class="canvas-wrapper">
            <canvas id="eulerCanvas" width="800" height="500"></canvas>
            
            <!-- LIVE MATH PANEL -->
            <div id="mathPanel" class="math-panel">
                <span class="math-label">Step Calculation:</span>
                <!-- y_new = y_old + y' * h -->
                <!-- Zmieniono &cdot; na &middot; aby uniknąć błędnego renderowania jako 'C z kropką' -->
                <div class="math-row">y<sub>new</sub> = y<sub>old</sub> + y' &middot; h</div>
                <div class="math-row" id="mathCalc">
                    <!-- JS will insert values here -->
                </div>
            </div>
        </div>

        <div class="legend">
            <div class="legend-item"><div class="line-sample" style="background:#1976d2"></div> Exact Solution</div>
            <div class="legend-item"><div class="dot" style="background:#d32f2f"></div><div class="line-sample" style="background:#d32f2f"></div> Numerical (Current)</div>
            <div class="legend-item"><div class="line-sample" style="background:#9e9e9e"></div> History</div>
            <div class="legend-item"><div class="line-sample" style="background:#f57f17; height: 4px;"></div> Tangent (y')</div>
        </div>
    </div>

    <script>
    (function() {
        const wrapper = document.querySelector('.euler-method-sim');
        const canvas = wrapper.querySelector('#eulerCanvas');
        const ctx = canvas.getContext('2d');
        const mathPanel = wrapper.querySelector('#mathPanel');
        const mathCalc = wrapper.querySelector('#mathCalc');
        
        const inputY0 = wrapper.querySelector('#input-y0');
        const inputH = wrapper.querySelector('#input-h');
        const inputSpeed = wrapper.querySelector('#input-speed');
        const btnRestart = wrapper.querySelector('#btn-restart');
        const btnClear = wrapper.querySelector('#btn-clear');
        
        const dispY0 = wrapper.querySelector('#disp-y0');
        const dispH = wrapper.querySelector('#disp-h');

        const LOGIC_X_MIN = -0.5;
        const LOGIC_X_MAX = 6.0;
        const LOGIC_Y_MIN = -10;
        const LOGIC_Y_MAX = 40;

        let state = {
            y0: 0,
            h: 1.0,
            animationSpeed: 500,
            currentStep: 0,
            isAnimating: false,
            points: [],
            history: [], 
            timer: null
        };

        // Differential equation: y' = 2x
        function derivative(x) { return 2 * x; }
        // Exact solution: y = x^2 + C
        function analytical(x, C) { return x * x + C; }

        function toScreen(lx, ly) {
            const w = canvas.width;
            const h = canvas.height;
            const sx = ((lx - LOGIC_X_MIN) / (LOGIC_X_MAX - LOGIC_X_MIN)) * w;
            const sy = h - ((ly - LOGIC_Y_MIN) / (LOGIC_Y_MAX - LOGIC_Y_MIN)) * h;
            return { x: sx, y: sy };
        }

        function drawGrid() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.strokeStyle = '#e0e0e0';
            ctx.lineWidth = 1;
            ctx.font = '12px sans-serif';
            ctx.fillStyle = '#666';

            // Vertical lines (X)
            for(let x = 0; x <= LOGIC_X_MAX; x += 1) {
                const p = toScreen(x, LOGIC_Y_MIN);
                const top = toScreen(x, LOGIC_Y_MAX);
                ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(top.x, top.y); ctx.stroke();
                ctx.fillText(x, p.x + 2, p.y - 5);
            }

            // Horizontal lines (Y)
            for(let y = -10; y <= LOGIC_Y_MAX; y += 10) {
                const p = toScreen(LOGIC_X_MIN, y);
                const right = toScreen(LOGIC_X_MAX, y);
                ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(right.x, right.y); ctx.stroke();
                ctx.fillText(y, p.x + 2, p.y - 2);
            }
            
            // Axes
            const origin = toScreen(0, 0);
            const xEnd = toScreen(LOGIC_X_MAX, 0);
            ctx.strokeStyle = '#333'; ctx.lineWidth = 2;
            ctx.beginPath(); ctx.moveTo(toScreen(LOGIC_X_MIN, 0).x, origin.y); ctx.lineTo(xEnd.x, xEnd.y); ctx.stroke();
            const yEnd = toScreen(0, LOGIC_Y_MAX);
            ctx.beginPath(); ctx.moveTo(origin.x, toScreen(0, LOGIC_Y_MIN).y); ctx.lineTo(origin.x, yEnd.y); ctx.stroke();
        }

        function drawExactCurve() {
            ctx.beginPath();
            ctx.strokeStyle = '#1976d2';
            ctx.lineWidth = 2;
            ctx.setLineDash([]);
            
            let first = true;
            for(let x = LOGIC_X_MIN; x <= LOGIC_X_MAX; x += 0.05) {
                const y = analytical(x, state.y0);
                const p = toScreen(x, y);
                if(first) { ctx.moveTo(p.x, p.y); first = false; }
                else { ctx.lineTo(p.x, p.y); }
            }
            ctx.stroke();
            
            ctx.fillStyle = '#1976d2';
            const txtP = toScreen(5.5, analytical(5.5, state.y0));
            if (txtP.y > 0 && txtP.y < canvas.height) {
                ctx.fillText("Exact", txtP.x - 30, txtP.y - 10);
            }
        }

        function drawHistory() {
            if (state.history.length === 0) return;
            
            ctx.lineWidth = 1;
            ctx.strokeStyle = '#9e9e9e'; 
            ctx.fillStyle = '#9e9e9e';

            state.history.forEach(run => {
                if(run.length < 2) return;
                
                ctx.beginPath();
                const p0 = toScreen(run[0].x, run[0].y);
                ctx.moveTo(p0.x, p0.y);
                for(let i=1; i<run.length; i++) {
                    const p = toScreen(run[i].x, run[i].y);
                    ctx.lineTo(p.x, p.y);
                }
                ctx.stroke();

                for(let i=0; i<run.length; i++) {
                    const p = toScreen(run[i].x, run[i].y);
                    ctx.beginPath();
                    ctx.arc(p.x, p.y, 2, 0, Math.PI*2);
                    ctx.fill();
                }
            });
        }

        function drawNumericalSoFar() {
            if (state.points.length === 0) return;

            ctx.beginPath();
            ctx.strokeStyle = '#d32f2f';
            ctx.lineWidth = 2;
            
            let p0 = toScreen(state.points[0].x, state.points[0].y);
            ctx.moveTo(p0.x, p0.y);

            const limit = Math.min(state.currentStep, state.points.length - 1);
            
            for(let i = 1; i <= limit; i++) {
                const p = toScreen(state.points[i].x, state.points[i].y);
                ctx.lineTo(p.x, p.y);
            }
            ctx.stroke();

            ctx.fillStyle = '#d32f2f';
            for(let i = 0; i <= limit; i++) {
                const p = toScreen(state.points[i].x, state.points[i].y);
                ctx.beginPath();
                ctx.arc(p.x, p.y, 4, 0, Math.PI*2);
                ctx.fill();
            }
        }

        // --- UPDATE MATH PANEL ---
        function updateMathPanel(curr, next, slope) {
            mathPanel.style.display = 'block';
            
            const yOldStr = curr.y.toFixed(2);
            const slopeStr = slope.toFixed(2);
            const hStr = state.h.toFixed(1);
            const yNewStr = next.y.toFixed(2);
            
            // Uses y' notation and &middot;
            mathCalc.innerHTML = `
                y<sub>new</sub> = <span class="val-old">${yOldStr}</span> + 
                <span class="val-slope">(${slopeStr})</span> &middot; 
                <span class="val-h">${hStr}</span> <br>
                y<sub>new</sub> = <span class="val-new">${yNewStr}</span>
            `;
        }

        function drawConstructionDetails() {
            if (state.currentStep >= state.points.length - 1) return;

            const curr = state.points[state.currentStep];
            const next = state.points[state.currentStep + 1];

            const pCurr = toScreen(curr.x, curr.y);
            const pNext = toScreen(next.x, next.y);
            const pBase = toScreen(next.x, curr.y);

            const slope = derivative(curr.x);
            
            // 1. Update Panel
            updateMathPanel(curr, next, slope);

            // 2. Construction Triangle
            ctx.fillStyle = 'rgba(255, 160, 0, 0.15)';
            ctx.beginPath();
            ctx.moveTo(pCurr.x, pCurr.y);
            ctx.lineTo(pBase.x, pBase.y); 
            ctx.lineTo(pNext.x, pNext.y); 
            ctx.fill();

            // 3. Tangent Line
            ctx.strokeStyle = '#f57f17'; 
            ctx.lineWidth = 3; 
            ctx.setLineDash([]); 
            ctx.beginPath();
            ctx.moveTo(pCurr.x, pCurr.y);
            ctx.lineTo(pNext.x, pNext.y);
            ctx.stroke();

            // 4. Vertical helper
            ctx.strokeStyle = '#f57f17';
            ctx.lineWidth = 1;
            ctx.setLineDash([4, 4]);
            ctx.beginPath();
            ctx.moveTo(pNext.x, pNext.y);
            ctx.lineTo(pNext.x, pBase.y); 
            ctx.stroke();

            // 5. Horizontal helper
            ctx.beginPath();
            ctx.moveTo(pCurr.x, pCurr.y);
            ctx.lineTo(pBase.x, pBase.y);
            ctx.stroke();
            ctx.setLineDash([]); 

            // Text on canvas
            ctx.fillStyle = '#ef6c00';
            ctx.font = 'bold 12px sans-serif';
            if (pCurr.x < canvas.width - 60 && pCurr.y > 30) {
                 // Using "y'" here as well
                 ctx.fillText(`y' = ${slope.toFixed(2)}`, pCurr.x + 10, pCurr.y - 15);
            }
            ctx.fillStyle = '#666';
            ctx.font = 'italic 11px sans-serif';
            ctx.fillText("h", (pCurr.x + pBase.x)/2, pBase.y + 12);
        }

        function calculatePoints() {
            state.points = [];
            let x = 0;
            let y = state.y0;
            state.points.push({x, y});

            while(x < LOGIC_X_MAX) {
                const slope = derivative(x);
                y = y + slope * state.h;
                x = x + state.h;
                if (x > LOGIC_X_MAX + state.h) break;
                state.points.push({x, y});
            }
        }

        function renderFrame() {
            drawGrid();
            drawHistory();      
            drawExactCurve();
            drawNumericalSoFar();
            
            if (state.isAnimating) {
                drawConstructionDetails();
            }
        }

        function nextAnimationStep() {
            if (state.currentStep < state.points.length - 1) {
                state.currentStep++;
                renderFrame();
                state.timer = setTimeout(nextAnimationStep, state.animationSpeed);
            } else {
                state.isAnimating = false;
                btnRestart.disabled = false;
                renderFrame();
            }
        }

        function startAnimation(shouldSaveHistory = true) {
            if(state.timer) clearTimeout(state.timer);
            
            mathPanel.style.display = 'none';

            if (shouldSaveHistory && state.points.length > 0) {
                state.history.push([...state.points]);
                if(state.history.length > 10) state.history.shift();
            }

            state.y0 = parseFloat(inputY0.value);
            state.h = parseFloat(inputH.value);
            
            const speedVal = parseInt(inputSpeed.value);
            state.animationSpeed = 1600 - (speedVal * 150);
            if (state.animationSpeed < 50) state.animationSpeed = 50;

            calculatePoints();
            state.currentStep = 0;
            state.isAnimating = true;
            btnRestart.disabled = true;

            renderFrame();
            state.timer = setTimeout(nextAnimationStep, state.animationSpeed);
        }

        // --- LISTENERS ---
        
        inputY0.addEventListener('input', () => { 
            dispY0.innerText = parseFloat(inputY0.value).toFixed(1);
        });
        
        inputY0.addEventListener('change', () => startAnimation(true));
        inputH.addEventListener('change', () => {
            dispH.innerText = parseFloat(inputH.value).toFixed(1);
            startAnimation(true);
        });
        
        inputH.addEventListener('input', () => {
            dispH.innerText = parseFloat(inputH.value).toFixed(1);
        });

        inputSpeed.addEventListener('input', () => {
             const speedVal = parseInt(inputSpeed.value);
             state.animationSpeed = 1600 - (speedVal * 150);
        });

        btnRestart.addEventListener('click', () => startAnimation(true));
        
        btnClear.addEventListener('click', () => {
            state.history = [];
            mathPanel.style.display = 'none';
            renderFrame();
        });

        // Init
        dispY0.innerText = parseFloat(inputY0.value).toFixed(1);
        dispH.innerText = parseFloat(inputH.value).toFixed(1);
        
        state.y0 = parseFloat(inputY0.value);
        calculatePoints(); 
        state.currentStep = 0;
        renderFrame();

    })();
    </script>
</div>

</body>
</html>
```


### Second order differential equations

Let's consider the second order differential equation

$$
\frac{d^2y(x)}{dx^2} = -y(x)
$$

This is a simple differential equation that can be solved by integration. The solution is

$$
y(x) = A \sin(x) + B \cos(x)
$$

where $A$ and $B$ are integration constants. This is the general solution of the differential equation. The solution is not unique because the constants $A$ and $B$ can take any value. The solution is a sinusoidal function. The constants $A$ and $B$ determine the amplitude and phase of the sinusoidal function.

#### Numerical solution

From Taylor's theorem, we know that the second derivative of a function $f(x)$ can be approximated by

$$
f(x + h) = f(x) + h \frac{df(x)}{dx} + \frac{h^2}{2} \frac{d^2f(x)}{dx^2} + \ldots
$$

also

$$
f(x - h) = f(x) - h \frac{df(x)}{dx} + \frac{h^2}{2} \frac{d^2f(x)}{dx^2} + \ldots
$$

adding these two equations we get

$$
f(x + h) + f(x - h) = 2 f(x) + h^2 \frac{d^2f(x)}{dx^2}
$$

so we can approximate the second derivative by

$$
\frac{f(x + h) + f(x - h) - 2 f(x)}{h^2} \approx \frac{d^2f(x)}{dx^2}
$$

Let's use this approximation to solve the differential equation

$$
\frac{d^2y(x)}{dx^2} = -y(x)
$$

We can approximate the second derivative by

$$
\frac{y(x + h) + y(x - h) - 2 y(x)}{h^2} = -y(x)
$$

This equation can be solved for $y(x + h)$

$$
y(x + h) = 2 y(x) - y(x - h) - h^2 y(x)
$$

This equation can be used to solve the differential equation numerically. We can start from an initial value of $y(x)$ and $y(x - h)$ and use the equation above to calculate the value of $y(x + h)$. This value can be used to calculate the next value of $y(x + 2h)$ and so on. This is a simple numerical method to solve differential equations.

Let us compare the numerical solution with the analytical solution.

```{python}
import numpy as np
import matplotlib.pyplot as plt

# Define the second derivative d^2y/dx^2
def d2y_dx2(x):
    return -x

# Define the exact analytical solution y(x) for comparison
def y_analytical(x):
    return -np.sin(x)

# Define a numerical solution for y(x) using the forward Euler method
def y_numerical(x, h):
    y = [0, np.sin(-h)]  # Initialize y with the starting values, assuming y(0) = 0 and y(-h) = sin(-h)
    for i in range(1, len(x) - 1):
        y.append(2 * y[-1] - y[-2] - h**2 * y[-1])  # Update y using d^2y/dx^2
    return y

# Set up the x values
plot_x = np.linspace(0, 10, 100)
x = np.linspace(0, 10, 20)
h = x[1] - x[0]  # Step size

# Compute the difference between analytical and numerical solutions
difference = y_analytical(x) - np.array(y_numerical(x, h))

# Set up the figure with two subplots
fig, axs = plt.subplots(2,1, figsize=(10,8))

# Left plot: Analytical and numerical solutions
axs[0].plot(plot_x, y_analytical(plot_x), label='Analytical', linestyle='dashed', linewidth=2)
axs[0].scatter(x, y_numerical(x, h), label='Numerical', color='red')
axs[0].set_title('Analytical vs Numerical')
axs[0].set_xlabel('x')

axs[0].set_ylabel('y')
axs[0].legend()

# Right plot: Difference between solutions
axs[1].scatter(x, difference, label='Difference')
axs[1].set_title('Difference (Analytical - Numerical)')
axs[1].set_xlabel('x')
axs[1].set_ylabel('Difference')
axs[1].legend()

# Adjust layout and show the plots
plt.tight_layout()
plt.show()
```


### Html example

```{=html}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Harmonic Oscillator Visualization</title>
    <style>
        body {
            margin: 0;
            padding: 20px;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #fff;
            display: flex;
            justify-content: center;
        }

        /* Namespace .harmonic-sim to isolate styles */
        .harmonic-sim {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0 auto;
            padding: 10px;
            background-color: #f9f9f9;
            border: 1px solid #ddd;
            border-radius: 8px;
            color: #333;
            width: 100%;
            max-width: 800px;
            box-sizing: border-box;
        }

        .harmonic-sim .sim-container {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 15px;
        }

        .harmonic-sim .canvas-wrapper {
            position: relative; 
            width: 100%;
            max-width: 700px;
            background: white;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.05);
        }

        .harmonic-sim canvas {
            display: block;
            width: 100%;
            height: auto;
            aspect-ratio: 16 / 9;
            cursor: default;
        }

        /* --- Math Panel Styles --- */
        .harmonic-sim .math-panel {
            position: absolute;
            top: 10px;
            right: 10px; /* ZMIANA: Panel przeniesiony na prawą stronę */
            background-color: rgba(255, 255, 255, 0.95);
            border: 1px solid #1976d2;
            border-left: 5px solid #1976d2;
            padding: 10px 15px;
            border-radius: 4px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            pointer-events: none;
            z-index: 10;
            display: none;
            line-height: 1.6;
            text-align: right; /* Opcjonalnie: wyrównanie tekstu do prawej dla estetyki */
        }

        .harmonic-sim .math-label {
            color: #666;
            font-weight: bold;
            font-size: 11px;
            text-transform: uppercase;
            margin-bottom: 2px;
            display: block;
            border-bottom: 1px solid #eee;
        }

        .harmonic-sim .math-row {
            white-space: nowrap;
        }

        .harmonic-sim .val-inertia { color: #757575; font-weight: bold; } /* Grey for straight line */
        .harmonic-sim .val-force { color: #f57f17; font-weight: bold; }   /* Orange for force */
        .harmonic-sim .val-new { color: #d32f2f; font-weight: bold; }     /* Red for result */

        /* Controls */
        .harmonic-sim .controls {
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
            justify-content: center;
            width: 100%;
            padding: 15px;
            background: #eee;
            border-radius: 6px;
        }

        .harmonic-sim .control-group {
            display: flex;
            flex-direction: column;
            align-items: center;
            min-width: 120px;
        }

        .harmonic-sim label {
            font-size: 0.9em;
            font-weight: 600;
            margin-bottom: 5px;
        }

        .harmonic-sim .val-display {
            font-family: monospace;
            color: #d32f2f;
            font-weight: bold;
        }

        .harmonic-sim .btn-group {
            display: flex;
            gap: 10px;
            align-items: center;
        }

        .harmonic-sim button {
            padding: 8px 16px;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            font-size: 14px;
            transition: background 0.2s;
            height: 40px;
        }

        .harmonic-sim #btn-restart { background-color: #1976d2; }
        .harmonic-sim #btn-restart:hover { background-color: #1565c0; }
        .harmonic-sim #btn-restart:disabled { background-color: #90caf9; cursor: not-allowed; }

        .harmonic-sim #btn-clear { background-color: #757575; }
        .harmonic-sim #btn-clear:hover { background-color: #616161; }
        
        .harmonic-sim input[type=range] {
            cursor: pointer;
        }

        .harmonic-sim .legend {
            font-size: 0.85em;
            display: flex;
            gap: 15px;
            margin-top: 5px;
            flex-wrap: wrap;
            justify-content: center;
        }
        
        .harmonic-sim .legend-item {
            display: flex;
            align-items: center;
            gap: 5px;
        }
        
        .harmonic-sim .dot { width: 10px; height: 10px; border-radius: 50%; }
        .harmonic-sim .line-sample { width: 20px; height: 3px; border-radius: 2px; }
        .harmonic-sim .dashed-sample { width: 20px; height: 2px; border-bottom: 2px dashed #757575; }

    </style>
</head>
<body>

<div class="harmonic-sim">
    <div class="sim-container">
        <div class="controls">
            <div class="control-group">
                <label>Position y(0): <span id="disp-y0" class="val-display">0.0</span></label>
                <input type="range" id="input-y0" min="-5" max="5" step="0.5" value="5">
            </div>
            <div class="control-group">
                <label>Velocity y'(0): <span id="disp-v0" class="val-display">0.0</span></label>
                <input type="range" id="input-v0" min="-5" max="5" step="0.5" value="0">
            </div>
            <div class="control-group">
                <label>Step size (h): <span id="disp-h" class="val-display">0.5</span></label>
                <input type="range" id="input-h" min="0.1" max="1.5" step="0.1" value="0.5">
            </div>
            <div class="control-group">
                <label>Speed</label>
                <input type="range" id="input-speed" min="1" max="10" step="1" value="5">
            </div>
            <div class="btn-group">
                <button id="btn-restart">Start</button>
                <button id="btn-clear">Clear</button>
            </div>
        </div>

        <div class="canvas-wrapper">
            <canvas id="harmonicCanvas" width="800" height="450"></canvas>
            
            <!-- LIVE MATH PANEL -->
            <div id="mathPanel" class="math-panel">
                <span class="math-label">2nd Order Step:</span>
                <!-- y_new = 2y_n - y_{n-1} - h^2 y_n -->
                <div class="math-row">
                    y<sub>n+1</sub> = 
                    <span class="val-inertia">2y<sub>n</sub> - y<sub>n-1</sub></span> 
                    <span class="val-force">- h<sup>2</sup>y<sub>n</sub></span>
                </div>
                <div class="math-row" id="mathCalc"></div>
            </div>
        </div>

        <div class="legend">
            <div class="legend-item"><div class="line-sample" style="background:#1976d2"></div> Exact (Harmonic)</div>
            <div class="legend-item"><div class="dot" style="background:#d32f2f"></div><div class="line-sample" style="background:#d32f2f"></div> Numerical</div>
            <div class="legend-item"><div class="dashed-sample"></div> Inertia (No force)</div>
            <div class="legend-item"><div class="line-sample" style="background:#f57f17; height: 4px;"></div> Force (-h²y)</div>
            <div class="legend-item"><div class="line-sample" style="background:#9e9e9e"></div> History</div>
        </div>
    </div>

    <script>
    (function() {
        const wrapper = document.querySelector('.harmonic-sim');
        const canvas = wrapper.querySelector('#harmonicCanvas');
        const ctx = canvas.getContext('2d');
        const mathPanel = wrapper.querySelector('#mathPanel');
        const mathCalc = wrapper.querySelector('#mathCalc');
        
        const inputY0 = wrapper.querySelector('#input-y0');
        const inputV0 = wrapper.querySelector('#input-v0');
        const inputH = wrapper.querySelector('#input-h');
        const inputSpeed = wrapper.querySelector('#input-speed');
        const btnRestart = wrapper.querySelector('#btn-restart');
        const btnClear = wrapper.querySelector('#btn-clear');
        
        const dispY0 = wrapper.querySelector('#disp-y0');
        const dispV0 = wrapper.querySelector('#disp-v0');
        const dispH = wrapper.querySelector('#disp-h');

        // Coordinate System Config
        const LOGIC_X_MIN = -0.5;
        const LOGIC_X_MAX = 20.0;
        const LOGIC_Y_MIN = -8;
        const LOGIC_Y_MAX = 8;

        let state = {
            y0: 5,
            v0: 0,
            h: 0.5,
            animationSpeed: 500,
            currentStep: 0, // Starts at 0 (index of points array)
            isAnimating: false,
            points: [],   // {x, y}
            history: [], 
            timer: null
        };

        // Exact solution: y'' = -y => y(x) = A sin(x) + B cos(x)
        // y(0) = B
        // y'(0) = A
        function analytical(x, y0, v0) {
            return v0 * Math.sin(x) + y0 * Math.cos(x);
        }

        function toScreen(lx, ly) {
            const w = canvas.width;
            const h = canvas.height;
            const sx = ((lx - LOGIC_X_MIN) / (LOGIC_X_MAX - LOGIC_X_MIN)) * w;
            const sy = h - ((ly - LOGIC_Y_MIN) / (LOGIC_Y_MAX - LOGIC_Y_MIN)) * h;
            return { x: sx, y: sy };
        }

        function drawGrid() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.strokeStyle = '#e0e0e0';
            ctx.lineWidth = 1;
            ctx.font = '12px sans-serif';
            ctx.fillStyle = '#666';

            // Vertical lines
            for(let x = 0; x <= LOGIC_X_MAX; x += 2) {
                const p = toScreen(x, LOGIC_Y_MIN);
                const top = toScreen(x, LOGIC_Y_MAX);
                ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(top.x, top.y); ctx.stroke();
                ctx.fillText(x, p.x + 2, p.y - 5);
            }

            // Horizontal lines
            for(let y = -5; y <= 5; y += 2.5) {
                if(y===0) continue; // Skip axis, draw later
                const p = toScreen(LOGIC_X_MIN, y);
                const right = toScreen(LOGIC_X_MAX, y);
                ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(right.x, right.y); ctx.stroke();
                ctx.fillText(y, p.x + 2, p.y - 2);
            }
            
            // Axes
            const origin = toScreen(0, 0);
            const xEnd = toScreen(LOGIC_X_MAX, 0);
            const yStart = toScreen(0, LOGIC_Y_MAX);
            const yEnd = toScreen(0, LOGIC_Y_MIN);

            ctx.strokeStyle = '#333'; ctx.lineWidth = 2;
            // X Axis
            ctx.beginPath(); ctx.moveTo(toScreen(LOGIC_X_MIN, 0).x, origin.y); ctx.lineTo(xEnd.x, xEnd.y); ctx.stroke();
            // Y Axis
            ctx.beginPath(); ctx.moveTo(origin.x, yStart.y); ctx.lineTo(origin.x, yEnd.y); ctx.stroke();
        }

        function drawExactCurve() {
            ctx.beginPath();
            ctx.strokeStyle = '#1976d2';
            ctx.lineWidth = 2;
            ctx.setLineDash([]);
            
            let first = true;
            for(let x = LOGIC_X_MIN; x <= LOGIC_X_MAX; x += 0.1) {
                const y = analytical(x, state.y0, state.v0);
                const p = toScreen(x, y);
                if(first) { ctx.moveTo(p.x, p.y); first = false; }
                else { ctx.lineTo(p.x, p.y); }
            }
            ctx.stroke();
        }

        function drawHistory() {
            if (state.history.length === 0) return;
            ctx.lineWidth = 1;
            ctx.strokeStyle = '#9e9e9e'; 
            ctx.fillStyle = '#9e9e9e';

            state.history.forEach(run => {
                if(run.length < 2) return;
                ctx.beginPath();
                const p0 = toScreen(run[0].x, run[0].y);
                ctx.moveTo(p0.x, p0.y);
                for(let i=1; i<run.length; i++) {
                    const p = toScreen(run[i].x, run[i].y);
                    ctx.lineTo(p.x, p.y);
                }
                ctx.stroke();
            });
        }

        function drawNumericalSoFar() {
            if (state.points.length === 0) return;

            ctx.beginPath();
            ctx.strokeStyle = '#d32f2f';
            ctx.lineWidth = 2;
            
            let p0 = toScreen(state.points[0].x, state.points[0].y);
            ctx.moveTo(p0.x, p0.y);

            // We draw lines up to currentStep
            // Note: points array includes the 'ghost' point at index 0 (x = -h)
            // We usually want to visualize starting from x=0 (index 1)
            
            const limit = Math.min(state.currentStep, state.points.length - 1);
            
            // Start drawing from index 1 (x=0) if it exists, otherwise index 0
            let startIdx = 1;
            if(state.points.length < 2) startIdx = 0;
            
            if(state.points.length > 1) {
                let pStart = toScreen(state.points[startIdx].x, state.points[startIdx].y);
                ctx.moveTo(pStart.x, pStart.y);
                
                for(let i = startIdx + 1; i <= limit; i++) {
                    const p = toScreen(state.points[i].x, state.points[i].y);
                    ctx.lineTo(p.x, p.y);
                }
                ctx.stroke();
            }

            ctx.fillStyle = '#d32f2f';
            for(let i = startIdx; i <= limit; i++) {
                const p = toScreen(state.points[i].x, state.points[i].y);
                ctx.beginPath();
                ctx.arc(p.x, p.y, 4, 0, Math.PI*2);
                ctx.fill();
            }
        }

        function updateMathPanel(curr, prev, next) {
            mathPanel.style.display = 'block';
            
            // Formula: y_new = 2*y_n - y_{n-1} - h^2*y_n
            // Which is: (Inertia part) + (Force part)
            // Inertia: 2*curr - prev
            // Force: -h^2 * curr
            
            const inertiaVal = 2 * curr.y - prev.y;
            const forceVal = -(state.h * state.h * curr.y);
            const nextVal = next.y;

            mathCalc.innerHTML = `
                y<sub>new</sub> = 
                <span class="val-inertia">(${inertiaVal.toFixed(2)})</span> 
                <span class="val-force">+ (${forceVal.toFixed(2)})</span> <br>
                y<sub>new</sub> = <span class="val-new">${nextVal.toFixed(2)}</span>
            `;
        }

        function drawConstructionDetails() {
            // We need at least 3 points to show the step: prev, curr, next
            
            if (state.currentStep < 1 || state.currentStep >= state.points.length - 1) return;

            const idx = state.currentStep; 
            const prev = state.points[idx - 1]; // y_{n-1}
            const curr = state.points[idx];     // y_n
            const next = state.points[idx + 1]; // y_{n+1}

            const pPrev = toScreen(prev.x, prev.y);
            const pCurr = toScreen(curr.x, curr.y);
            const pNext = toScreen(next.x, next.y);

            // 1. Inertia Point (Project straight line)
            // y_inertia = 2*curr - prev = curr + (curr - prev)
            const inertiaY = 2 * curr.y - prev.y;
            const pInertia = toScreen(next.x, inertiaY);

            updateMathPanel(curr, prev, next);

            // Draw line extension (Inertia)
            ctx.beginPath();
            ctx.strokeStyle = '#757575';
            ctx.lineWidth = 1;
            ctx.setLineDash([5, 5]);
            ctx.moveTo(pPrev.x, pPrev.y);
            ctx.lineTo(pInertia.x, pInertia.y); // Pass through curr to inertia
            ctx.stroke();
            
            // Mark inertia point
            ctx.fillStyle = '#757575';
            ctx.beginPath(); ctx.arc(pInertia.x, pInertia.y, 3, 0, 6.28); ctx.fill();

            // 2. Force Vector (Drop from Inertia to Next)
            ctx.beginPath();
            ctx.strokeStyle = '#f57f17'; // Orange
            ctx.lineWidth = 3;
            ctx.setLineDash([]);
            ctx.moveTo(pInertia.x, pInertia.y);
            ctx.lineTo(pNext.x, pNext.y);
            ctx.stroke();

            // Arrow head
            const headLen = 6;
            const angle = Math.atan2(pNext.y - pInertia.y, pNext.x - pInertia.x);
            ctx.beginPath();
            ctx.moveTo(pNext.x, pNext.y);
            ctx.lineTo(pNext.x - headLen * Math.cos(angle - Math.PI/6), pNext.y - headLen * Math.sin(angle - Math.PI/6));
            ctx.lineTo(pNext.x - headLen * Math.cos(angle + Math.PI/6), pNext.y - headLen * Math.sin(angle + Math.PI/6));
            ctx.fillStyle = '#f57f17';
            ctx.fill();

            // Text Labels
            ctx.font = '11px sans-serif';
            ctx.fillStyle = '#757575';
            ctx.fillText("Inertia", pInertia.x + 5, pInertia.y);
            
            ctx.fillStyle = '#f57f17';
            ctx.fillText("Force (-h²y)", pInertia.x + 5, (pInertia.y + pNext.y)/2);
        }

        function calculatePoints() {
            state.points = [];
            
            // We need y(0) and y(-h) to start.
            
            const x0 = 0;
            const y0 = state.y0;
            const h = state.h;
            
            // Ghost point at x = -h
            const x_ghost = -h;
            const y_ghost = y0 * (1 - 0.5 * h * h) - h * state.v0;
            
            state.points.push({x: x_ghost, y: y_ghost}); // Index 0
            state.points.push({x: x0, y: y0});           // Index 1 (Start)

            let x = x0;
            let y_curr = y0;
            let y_prev = y_ghost;

            while(x < LOGIC_X_MAX) {
                // Verlet step: y_next = 2*y_curr - y_prev - h^2 * y_curr
                //               = (2 - h^2)*y_curr - y_prev
                const y_next = (2 - h*h) * y_curr - y_prev;
                const x_next = x + h;
                
                state.points.push({x: x_next, y: y_next});
                
                y_prev = y_curr;
                y_curr = y_next;
                x = x_next;
            }
        }

        function renderFrame() {
            drawGrid();
            drawHistory();      
            drawExactCurve();
            drawNumericalSoFar();
            
            if (state.isAnimating) {
                drawConstructionDetails();
            }
        }

        function nextAnimationStep() {
            if (state.currentStep < state.points.length - 2) { 
                // We stop before the very last point so we can show construction of the last one
                // currentStep tracks the "center" point of the verlet stencil
                state.currentStep++;
                renderFrame();
                state.timer = setTimeout(nextAnimationStep, state.animationSpeed);
            } else {
                state.currentStep++; // Draw final state
                state.isAnimating = false;
                btnRestart.disabled = false;
                renderFrame();
            }
        }

        function startAnimation(shouldSaveHistory = true) {
            if(state.timer) clearTimeout(state.timer);
            mathPanel.style.display = 'none';

            // Points array starts with ghost point (idx 0), then y0 (idx 1).
            // We want to save history starting from idx 1 (actual trace)
            if (shouldSaveHistory && state.points.length > 1) {
                const visibleTrace = state.points.slice(1);
                state.history.push(visibleTrace);
                if(state.history.length > 8) state.history.shift();
            }

            state.y0 = parseFloat(inputY0.value);
            state.v0 = parseFloat(inputV0.value);
            state.h = parseFloat(inputH.value);
            
            const speedVal = parseInt(inputSpeed.value);
            state.animationSpeed = 1600 - (speedVal * 150);
            if (state.animationSpeed < 50) state.animationSpeed = 50;

            calculatePoints();
            
            // Start animation from index 1 (x=0 is established, we show construction of x=h)
            state.currentStep = 1; 
            state.isAnimating = true;
            btnRestart.disabled = true;

            renderFrame();
            state.timer = setTimeout(nextAnimationStep, state.animationSpeed);
        }

        // --- LISTENERS ---
        function updateDisplays() {
            dispY0.innerText = parseFloat(inputY0.value).toFixed(1);
            dispV0.innerText = parseFloat(inputV0.value).toFixed(1);
            dispH.innerText = parseFloat(inputH.value).toFixed(1);
        }
        
        // Inputs trigger restart on change
        [inputY0, inputV0, inputH].forEach(input => {
            input.addEventListener('input', updateDisplays);
            input.addEventListener('change', () => startAnimation(true));
        });

        inputSpeed.addEventListener('input', () => {
             const speedVal = parseInt(inputSpeed.value);
             state.animationSpeed = 1600 - (speedVal * 150);
        });

        btnRestart.addEventListener('click', () => startAnimation(true));
        
        btnClear.addEventListener('click', () => {
            state.history = [];
            mathPanel.style.display = 'none';
            renderFrame();
        });

        // Init
        updateDisplays();
        state.y0 = parseFloat(inputY0.value);
        state.v0 = parseFloat(inputV0.value);
        state.h = parseFloat(inputH.value);
        calculatePoints(); 
        
        // Initial render: show up to start point
        state.currentStep = 1;
        state.isAnimating = false;
        renderFrame();

    })();
    </script>
</div>

</body>
</html>
```

## Gradient

The **gradient** of a scalar field is itself a vector field that points in the direction of the greatest rate of increase of that scalar field. Its magnitude indicates how rapidly the value of the scalar field changes. Formally, for a function $f(x, y, z)$, the gradient is:

$$
\nabla f = \left( \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}, \frac{\partial f}{\partial z} \right).
$$

If a function $f$ is defined in 2D space as $f(x, y)$, then

$$
\nabla f(x, y) = \left( \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y} \right).
$$

::: warning
Any scalar field $f(x, y)$ has a corresponding gradient field $\nabla f$. **However**, not every vector field is necessarily the gradient of some scalar field.  
:::

---

### 1D Example

For a one-dimensional function $f(x) = \sin(x)$ on the interval $[0, 10]$, the gradient (or in this 1D case, simply the derivative) is:

$$
f'(x) = \cos(x).
$$

Below is a Python example that illustrates how to compute these derivatives at a few sample points. We then represent them as small vectors placed at $y = 2$ in a plot, pointing upward if $f'(x)$ is positive and downward if $f'(x)$ is negative, with a scaled magnitude.

```{python}
import numpy as np
import matplotlib.pyplot as plt

# 1D function
def f(x):
    return np.sin(x)

# Its derivative (gradient in 1D)
def df(x):
    return np.cos(x)

# Sample points on [0, 10]
xs = np.linspace(0, 10, 100)
values_f = f(xs)

# Choose a few points to visualize gradient vectors
sample_points = np.linspace(0, 10, 20)
grad_values = df(sample_points)

fig, ax = plt.subplots(figsize=(8, 4))

# Plot sin(x)
ax.plot(xs, values_f, label='f(x) = sin(x)')

# Plot gradient vectors at y=2 for each sample point
for x_i, grad in zip(sample_points, grad_values):
    # We'll scale the arrow length by 0.5 for visibility
    ax.arrow(
        x_i, f(x_i), grad,0 ,
        head_width=0.1, head_length=0.1, length_includes_head=True,
        fc='green', ec='red'
    )

ax.set_ylim(-1.5, 2)
ax.set_xlabel('x')
ax.set_ylabel('f(x)')
ax.set_title('1D Gradient (Derivative) of sin(x)')
ax.legend()
plt.show()
```

In this plot:
- The blue curve is $\sin(x)$.
- The red arrows at show the derivative $f'(x) = \cos(x)$ at the chosen sample points.
  - Arrows pointing up indicate a positive derivative.


---

### 2D Example

Now let’s consider a scalar field in two variables. For instance:

$$
f(x, y) = \sin(x)\cos(y).
$$

Let us compute partial derivatives of this function. Derivative with respect to $x$ is:

$$
\frac{\partial f(x,y)}{\partial x} = \frac{\partial}{\partial x}[\sin(x)\cos(y)] = \cos(x)\cos(y).
$$

Derivative with respect to $y$ is:

$$
\frac{\partial f(x,y)}{\partial y} = \frac{\partial}{\partial y}[\sin(x)\cos(y)] = -\sin(x)\sin(y).
$$

so the gradient of the function $f(x, y)$ is:

$$
\nabla f(x, y) = 
\bigl(\cos(x)\cos(y),\; -\sin(x)\sin(y)\bigr).
$$

```{python}
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# Define the 2D scalar field
def f2d(x, y):
    return np.sin(x) * np.cos(y)

# Create a grid for plotting
nx, ny = 60, 60
x_vals = np.linspace(0, 2*np.pi, nx)
y_vals = np.linspace(0, 2*np.pi, ny)
X, Y = np.meshgrid(x_vals, y_vals)
Z = f2d(X, Y)

# --- 3D Surface Plot ---
fig3d = plt.figure(figsize=(8, 6))
ax3d = fig3d.add_subplot(111, projection='3d')

# Create a surface plot
surf = ax3d.plot_surface(
    X, Y, Z, 
    cmap='coolwarm', 
    edgecolor='none',
    alpha=0.9
)
fig3d.colorbar(surf, ax=ax3d, shrink=0.6, label='f(x,y) = sin(x)*cos(y)')
ax3d.set_xlabel('x')
ax3d.set_ylabel('y')
# ax3d.set_zlabel('f(x,y)')
ax3d.set_title('3D Surface of f(x, y) = sin(x)*cos(y)')
plt.show()
```


Below is a Python example that:
1. Plots a heatmap of $f(x, y)$.
2. Overlays gradient vectors (arrows) on a grid of points.

```{python}
import numpy as np
import matplotlib.pyplot as plt

# Define the 2D scalar field
def f2d(x, y):
    return np.sin(x) * np.cos(y)

# Define partial derivatives (gradient)
def grad_f2d(x, y):
    # df/dx = cos(x)*cos(y)
    # df/dy = -sin(x)*sin(y)
    return np.cos(x)*np.cos(y), -2*np.sin(x)*np.sin(y)

# Create a grid for plotting
nx, ny = 60, 60
x_vals = np.linspace(0, 2*np.pi, nx)
y_vals = np.linspace(0, 2*np.pi, ny)
X, Y = np.meshgrid(x_vals, y_vals)
Z = f2d(X, Y)

# Compute gradient on a coarser grid for quiver plot
skip = 3
x_quiv = X[::skip, ::skip]
y_quiv = Y[::skip, ::skip]
Fx, Fy = grad_f2d(x_quiv, y_quiv)

fig, ax = plt.subplots(figsize=(8, 6))

# Heatmap of f(x,y)
c = ax.imshow(
    Z, 
    extent=[x_vals.min(), x_vals.max(), y_vals.min(), y_vals.max()],
    origin='lower',
    cmap='coolwarm'
)
fig.colorbar(c, ax=ax, label='f(x,y) = sin(x)*cos(y)')

# Quiver plot of gradient vectors
ax.quiver(
    x_quiv, y_quiv, Fx, Fy, 
    color='black', 
    pivot='mid', 
    alpha=0.8,
    width=0.003,
    scale=50
)

ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('Gradient of f(x, y) = sin(x)*cos(y)')
plt.show()
```

**Interpretation**:

- The **heatmap** shows the values of $f(x,y)$. Red/blue corresponds to high/low values of the scalar field.
- The **arrows** show the local direction and magnitude of the gradient, i.e., where $f(x, y)$ increases the most and how quickly.

This demonstrates how any scalar field (like temperature, potential, or pressure) naturally defines a vector field via its gradient. However, **not every vector field arises as a gradient** of a scalar field—certain mathematical conditions (like having zero curl in a simply connected domain) must be satisfied for a vector field to be the gradient of some scalar field.


#### Html example

```{=html}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gradient Descent Explorer 1D</title>
    <style>
        body {
            margin: 0;
            padding: 20px;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #fff;
            display: flex;
            justify-content: center;
        }

        /* Namespace .gd-1d-sim */
        .gd-1d-sim {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0 auto;
            background-color: #fcfcfc;
            border: 1px solid #ddd;
            border-radius: 8px;
            color: #333;
            width: 100%;
            max-width: 900px;
            box-sizing: border-box;
            display: flex;
            flex-direction: column;
            box-shadow: 0 4px 15px rgba(0,0,0,0.05);
            overflow: hidden;
        }

        .gd-1d-sim header {
            background-color: #fff;
            padding: 15px 20px;
            border-bottom: 1px solid #eee;
        }

        .gd-1d-sim h3 {
            margin: 0;
            color: #2c3e50;
            font-size: 1.2rem;
        }

        .gd-1d-sim .main-content {
            display: flex;
            flex-direction: column;
            position: relative;
        }

        .gd-1d-sim canvas {
            display: block;
            background: #ffffff;
            cursor: crosshair;
            width: 100%;
            height: 400px; /* Fixed height for canvas logic */
        }

        /* CONTROLS & MATH CONTAINER */
        .gd-1d-sim .controls {
            padding: 20px;
            background: #f1f3f4;
            border-top: 1px solid #ddd;
            display: flex;
            flex-wrap: wrap;
            gap: 20px;
            align-items: center;
            justify-content: center;
        }

        /* NEW MATH PANEL STYLE (Embedded in controls) */
        .gd-1d-sim .math-panel {
            width: 100%; /* Take full width of the controls container */
            background: #fff;
            border: 1px solid #ccc;
            border-left: 4px solid #d32f2f;
            padding: 15px 20px;
            border-radius: 4px;
            margin-bottom: 5px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.05);
            font-family: 'Courier New', monospace;
            font-size: 0.95rem;
            display: none; /* Hidden by default */
        }

        .gd-1d-sim .math-row {
            margin-bottom: 8px;
        }
        .gd-1d-sim .math-title {
            font-family: sans-serif;
            font-weight: bold;
            color: #555;
            font-size: 0.8rem;
            margin-bottom: 8px;
            display: block;
            text-transform: uppercase;
            border-bottom: 1px solid #eee;
            padding-bottom: 4px;
        }

        .gd-1d-sim .val-x { color: #1976d2; font-weight: bold; }
        .gd-1d-sim .val-slope { color: #d32f2f; font-weight: bold; }
        .gd-1d-sim .val-lr { color: #2e7d32; font-weight: bold; }
        .gd-1d-sim .val-new { color: #333; font-weight: bold; background: #eee; padding: 0 4px; }

        /* INPUTS & BUTTONS */
        .gd-1d-sim .control-group {
            display: flex;
            flex-direction: column;
            gap: 5px;
        }

        .gd-1d-sim label {
            font-weight: 600;
            font-size: 0.85rem;
            color: #555;
        }

        .gd-1d-sim input[type=range] {
            cursor: pointer;
            width: 150px;
        }

        .gd-1d-sim button {
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            font-size: 14px;
            transition: background 0.2s, transform 0.1s;
        }

        .gd-1d-sim .btn-step {
            background-color: #f57c00; 
            color: white;
            min-width: 120px;
        }
        .gd-1d-sim .btn-step:hover { background-color: #ef6c00; }
        .gd-1d-sim .btn-step:active { transform: translateY(1px); }

        .gd-1d-sim .btn-run {
            background-color: #2e7d32; 
            color: white;
            min-width: 100px;
        }
        .gd-1d-sim .btn-run:hover { background-color: #1b5e20; }

        .gd-1d-sim .btn-reset {
            background-color: #607d8b;
            color: white;
        }
        .gd-1d-sim .btn-reset:hover { background-color: #455a64; }

        .gd-1d-sim .instructions {
            margin-top: 10px;
            font-size: 0.9rem;
            color: #666;
            text-align: center;
            width: 100%;
        }

    </style>
</head>
<body>

<div class="gd-1d-sim">
    <header>
        <h3>Gradient Descent: Finding the Minimum</h3>
    </header>

    <div class="main-content">
        <canvas id="gdCanvas" width="900" height="400"></canvas>
    </div>

    <div class="controls">
        <!-- MATH PANEL MOVED HERE -->
        <div class="math-panel" id="mathBox">
            <span class="math-title">Current Calculation</span>
            <div style="display: flex; flex-wrap: wrap; gap: 20px; align-items: center;">
                <div class="math-row">
                    Slope (f'(x)) = <span id="disp-slope" class="val-slope">0.00</span>
                </div>
                <div class="math-row">
                    x<sub>new</sub> = <span id="disp-x-old" class="val-x">0.00</span> - (<span id="disp-lr" class="val-lr">0.1</span> &middot; <span class="val-slope">Slope</span>)
                </div>
                <div class="math-row">
                    &rarr; x<sub>new</sub> = <span id="disp-x-new" class="val-new">0.00</span>
                </div>
            </div>
            <div style="font-size: 0.8em; color: #777; margin-top:5px;">
                <em>Ball rolls opposite to the slope (Gradient Descent Step).</em>
            </div>
        </div>

        <div class="control-group">
            <label>Learning Rate (Step Size)</label>
            <input type="range" id="lrSlider" min="0.01" max="1.0" step="0.01" value="0.2">
            <span style="font-size: 0.8rem; color:#555; text-align: center;" id="lrVal">0.20</span>
        </div>

        <button class="btn-step" id="btnStep">Take 1 Step</button>
        <button class="btn-run" id="btnRun">Auto Run</button>
        <button class="btn-reset" id="btnReset">Clear</button>

        <div class="instructions">
            <strong>Click anywhere on the curve</strong> to place the ball, then use controls to descend.
        </div>
    </div>

    <script>
    (function() {
        const canvas = document.getElementById('gdCanvas');
        const ctx = canvas.getContext('2d');
        const mathBox = document.getElementById('mathBox');
        
        // Displays
        const dispSlope = document.getElementById('disp-slope');
        const dispXOld = document.getElementById('disp-x-old');
        const dispLr = document.getElementById('disp-lr');
        const dispXNew = document.getElementById('disp-x-new');
        const lrSlider = document.getElementById('lrSlider');
        const lrVal = document.getElementById('lrVal');

        // Buttons
        const btnStep = document.getElementById('btnStep');
        const btnRun = document.getElementById('btnRun');
        const btnReset = document.getElementById('btnReset');

        // Simulation State
        const LOGIC_X_MIN = -4;
        const LOGIC_X_MAX = 4;
        // Y range determined dynamically or fixed
        const LOGIC_Y_MIN = -2;
        const LOGIC_Y_MAX = 6;

        let ball = {
            x: null, // Logic X
            exists: false,
            path: [] // History of points
        };
        
        let isRunning = false;
        let animationFrame = null;

        // --- THE FUNCTION ---
        // f(x) = 0.3*x^2 + cos(3*x) + 0.5*x
        function f(x) {
            return 0.3 * x * x - Math.cos(2 * x) + 0.5 * Math.sin(x) + 1.5; 
        }

        // Derivative f'(x)
        function df(x) {
            return 0.6 * x + 2 * Math.sin(2 * x) + 0.5 * Math.cos(x);
        }

        // --- Coordinates ---
        function toScreen(lx, ly) {
            const w = canvas.width;
            const h = canvas.height;
            const padding = 40;
            
            const sx = padding + ((lx - LOGIC_X_MIN) / (LOGIC_X_MAX - LOGIC_X_MIN)) * (w - 2*padding);
            const sy = h - padding - ((ly - LOGIC_Y_MIN) / (LOGIC_Y_MAX - LOGIC_Y_MIN)) * (h - 2*padding);
            return { x: sx, y: sy };
        }

        function toLogic(sx, sy) {
            const w = canvas.width;
            const h = canvas.height;
            const padding = 40;
            
            const lx = LOGIC_X_MIN + ((sx - padding) / (w - 2*padding)) * (LOGIC_X_MAX - LOGIC_X_MIN);
            return lx;
        }

        // --- Drawing ---
        function drawCurve() {
            ctx.beginPath();
            ctx.strokeStyle = '#333';
            ctx.lineWidth = 3;
            
            let first = true;
            const step = (LOGIC_X_MAX - LOGIC_X_MIN) / 400;
            
            for(let x = LOGIC_X_MIN; x <= LOGIC_X_MAX; x += step) {
                const y = f(x);
                const p = toScreen(x, y);
                if (first) { ctx.moveTo(p.x, p.y); first = false; }
                else { ctx.lineTo(p.x, p.y); }
            }
            ctx.stroke();
        }

        function drawGrid() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            const origin = toScreen(0, 0);
            
            ctx.beginPath();
            ctx.strokeStyle = '#eee';
            ctx.lineWidth = 1;
            
            // Y Axis
            ctx.moveTo(origin.x, 0);
            ctx.lineTo(origin.x, canvas.height);
            
            // X Axis
            const y0 = toScreen(0, 0).y;
            ctx.moveTo(0, y0);
            ctx.lineTo(canvas.width, y0);
            ctx.stroke();
        }

        function drawBall() {
            if (!ball.exists) return;
            
            const y = f(ball.x);
            const p = toScreen(ball.x, y);
            const slope = df(ball.x);

            // 1. Draw Tangent Line (Red)
            const tanLen = 0.8; 
            const x1 = ball.x - tanLen;
            const y1 = slope * (x1 - ball.x) + y;
            const x2 = ball.x + tanLen;
            const y2 = slope * (x2 - ball.x) + y;
            
            const p1 = toScreen(x1, y1);
            const p2 = toScreen(x2, y2);
            
            ctx.beginPath();
            ctx.strokeStyle = '#d32f2f';
            ctx.lineWidth = 2;
            ctx.moveTo(p1.x, p1.y);
            ctx.lineTo(p2.x, p2.y);
            ctx.stroke();

            // 2. Draw Movement Vector (Green Arrow)
            const lr = parseFloat(lrSlider.value);
            const stepX = -lr * slope; 
            const visScale = 1.0; 
            const vecEnd = toScreen(ball.x + stepX * visScale, y);
            
            ctx.beginPath();
            ctx.strokeStyle = '#2e7d32';
            ctx.lineWidth = 4;
            ctx.moveTo(p.x, p.y);
            ctx.lineTo(vecEnd.x, vecEnd.y);
            ctx.stroke();
            
            // Arrowhead
            const angle = stepX > 0 ? 0 : Math.PI; 
            ctx.beginPath();
            ctx.fillStyle = '#2e7d32';
            ctx.moveTo(vecEnd.x, vecEnd.y);
            if (stepX > 0) { 
                ctx.lineTo(vecEnd.x - 8, vecEnd.y - 5);
                ctx.lineTo(vecEnd.x - 8, vecEnd.y + 5);
            } else { 
                ctx.lineTo(vecEnd.x + 8, vecEnd.y - 5);
                ctx.lineTo(vecEnd.x + 8, vecEnd.y + 5);
            }
            ctx.fill();


            // 3. Draw Path (Ghost trails)
            ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
            for(let pt of ball.path) {
                const pp = toScreen(pt.x, f(pt.x));
                ctx.beginPath();
                ctx.arc(pp.x, pp.y, 3, 0, Math.PI*2);
                ctx.fill();
            }

            // 4. Draw Ball
            ctx.beginPath();
            ctx.fillStyle = '#d32f2f';
            ctx.arc(p.x, p.y, 8, 0, Math.PI*2);
            ctx.fill();
            ctx.strokeStyle = 'white';
            ctx.lineWidth = 2;
            ctx.stroke();

            // Update Math Panel
            updateMath(ball.x, slope, stepX);
        }

        function updateMath(x, slope, step) {
            mathBox.style.display = 'block';
            dispXOld.innerText = x.toFixed(3);
            dispSlope.innerText = slope.toFixed(3);
            dispLr.innerText = parseFloat(lrSlider.value).toFixed(2);
            
            // x_new = x + step
            const xNew = x + step;
            dispXNew.innerText = xNew.toFixed(3);
        }

        function render() {
            drawGrid();
            drawCurve();
            drawBall();
        }

        // --- Algorithms ---

        function calculateStep() {
            if (!ball.exists) return;
            
            const lr = parseFloat(lrSlider.value);
            const slope = df(ball.x);
            
            ball.path.push({x: ball.x});
            
            const step = -lr * slope;
            ball.x += step;
            
            // Boundary checks
            if (ball.x < LOGIC_X_MIN) ball.x = LOGIC_X_MIN;
            if (ball.x > LOGIC_X_MAX) ball.x = LOGIC_X_MAX;
            
            render();
            
            // Check convergence
            if (Math.abs(slope) < 0.001) {
                return false; 
            }
            return true;
        }

        function runAnimation() {
            if (!isRunning) return;
            const keepGoing = calculateStep();
            
            if (keepGoing) {
                animationFrame = requestAnimationFrame(() => {
                    setTimeout(runAnimation, 100); 
                });
            } else {
                isRunning = false;
                btnRun.innerText = "Auto Run";
            }
        }

        // --- Interaction ---

        canvas.addEventListener('mousedown', (e) => {
            isRunning = false;
            cancelAnimationFrame(animationFrame);
            btnRun.innerText = "Auto Run";

            const rect = canvas.getBoundingClientRect();
            const lx = toLogic(e.clientX - rect.left, e.clientY - rect.top);
            
            ball.x = lx;
            ball.exists = true;
            ball.path = []; 
            
            render();
        });

        btnStep.addEventListener('click', () => {
            if (!ball.exists) {
                alert("Click on the graph first to place the ball!");
                return;
            }
            isRunning = false;
            calculateStep();
        });

        btnRun.addEventListener('click', () => {
            if (!ball.exists) {
                alert("Click on the graph first to place the ball!");
                return;
            }
            
            if (isRunning) {
                isRunning = false;
                cancelAnimationFrame(animationFrame);
                btnRun.innerText = "Auto Run";
            } else {
                isRunning = true;
                btnRun.innerText = "Stop";
                runAnimation();
            }
        });

        btnReset.addEventListener('click', () => {
            ball.exists = false;
            ball.path = [];
            mathBox.style.display = 'none';
            isRunning = false;
            btnRun.innerText = "Auto Run";
            render();
        });
        
        lrSlider.addEventListener('input', () => {
            lrVal.innerText = parseFloat(lrSlider.value).toFixed(2);
            if (ball.exists) render(); 
        });

        // Init
        render();

    })();
    </script>
</div>

</body>
</html>

```